Compare commits

...

90 commits

Author SHA1 Message Date
dfd3c5d66e
build: update all dependecies for zig-0.15.1 2025-08-22 14:22:15 +02:00
b6d4935303
build: update build to zig-0.15.1 2025-08-22 13:16:20 +02:00
55a862eac0
Merge branch 'master' into zig-0.15.0 2025-08-22 13:12:37 +02:00
a227eb925c
refactor: unvendor flow-syntax 2025-08-21 10:45:21 +02:00
21bd1e58a8
refactor: output LSP client messages to log
Regular message only if lsp_output "verbose".
2025-08-20 21:15:38 +02:00
69ea495495
feat: add config option lsp_output "quiet" to reduce LSP log verbosity
Set the option to "verbose" to re-enable logging of LSP show/logMessage requests.
2025-08-20 21:00:24 +02:00
72423471f1
feat: store LSP log file name in LSP client 2025-08-19 11:59:52 +02:00
d0e175a5d7
fix: use OS path separator when creating LSP log file 2025-08-19 11:59:17 +02:00
9c926883f8
refactor: send LSP client messages to project_manager instead of log 2025-08-19 11:57:40 +02:00
9bc25620cb
fix: over eager whitespace collapsing in smart_insert_line 2025-08-18 21:07:49 +02:00
9d127e4cc3
fix: add support for kitty mouse leave events
This prevents reporting kitty mouse leave events as spurious mouse clicks.
2025-08-18 15:14:50 +02:00
d53a24a1d2
fix: make open_recent_projects run async 2025-08-18 14:47:01 +02:00
7d7a45f539
refactor: make project_manager.request_recent_projects fully async 2025-08-18 14:46:13 +02:00
cdd1e09069
feat: add support for loading palettes with arguments 2025-08-18 14:42:34 +02:00
06a31ea5fd
fix: mark find in files results as byte positions 2025-08-17 22:33:24 +02:00
4188e25df9
feat: add support for specifying byte positions in filelist_view 2025-08-17 22:32:44 +02:00
7207b0435e
feat: add support for specifying positions in bytes in goto_line_and_column 2025-08-17 22:31:15 +02:00
0a60f37857
build: update libvaxis 2025-08-16 22:47:33 +02:00
Alex Rønne Petersen
523085ba70 file_types: invoke OmniSharp instead of omnisharp
The former is the canonical executable name, and also what Helix uses. Seems reasonable to standardize on that.
2025-08-16 21:26:26 +02:00
057a9d60cd
feat: add completion palette 2025-08-15 23:30:54 +02:00
961090140a
refactor: split render_file_item_cbor into two functions for better reuse 2025-08-15 23:26:13 +02:00
70efcc8693
feat: add no_store argument to set_theme command for cli use 2025-08-15 12:55:59 +02:00
3b3104e876
feat: add flow mode keybind for find_in_files to overlay/palette mode 2025-08-15 12:31:54 +02:00
b043dfe34f
feat: reduce mode indicator clutter 2025-08-15 11:27:08 +02:00
6b04f4db08
fix: entering the same minimode twice causes an empty keybind set 2025-08-15 11:22:43 +02:00
62b8493b93
fix: correct match offsets when show_fileicons is off 2025-08-14 16:30:46 +02:00
c143eb6a59
fix: also hide inputbox icons if show_fileicons config option is off 2025-08-14 16:29:46 +02:00
aaa3e5b079
feat: tweak home screen widget style 2025-08-14 16:11:12 +02:00
e8c780b3b7
refactor: re-order widget style tags 2025-08-14 16:04:25 +02:00
4beedaf1aa
refactor: make tui.next_widget_style tag order independant 2025-08-14 16:03:44 +02:00
0c19cbd82d
feat: persist widget style changes 2025-08-14 15:55:09 +02:00
a27c212461
feat: add widget style switching command to open_recent palette 2025-08-13 22:53:26 +02:00
e95b232184
feat: add more widget box styles 2025-08-13 22:50:07 +02:00
c67c0b0c94
feat: add style switching command to palette (alt+f9) 2025-08-13 22:35:58 +02:00
af9b097077
fix: rename run_task command on home screen 2025-08-13 22:33:51 +02:00
17b3f152d5
feat: add style switching command (alt+f9) to home screen 2025-08-13 22:33:17 +02:00
4f912cebeb
feat: add basic widget style switching support 2025-08-13 22:32:31 +02:00
d872e2e734
feat: add palette_menu_delete_item hint to task_palette 2025-08-13 19:20:04 +02:00
8107a0d2b8
feat: display command hints in task_palette 2025-08-13 19:19:45 +02:00
cef495cb53
feat: make add_task directly run the new task if called interactively 2025-08-13 19:18:28 +02:00
7bd9c972e7
feat: add flow mode keybindings for add_task 2025-08-13 19:17:52 +02:00
5ce458e636
feat: add icon to add_task command meta 2025-08-13 19:17:34 +02:00
5f77a48afe
feat: add description and icon for palette_menu_delete_item command 2025-08-13 19:16:32 +02:00
469e10d4d9
feat: add support for icons to command module 2025-08-13 19:13:37 +02:00
cea8edecb9
feat: rename select_task to run_task and add a string parameter 2025-08-13 18:03:17 +02:00
c640c3f04b
fix: task_palette should not pass palette entry text to add_task 2025-08-13 17:52:23 +02:00
2414f3b00f
feat: add string parameter to add_task command for cli usage 2025-08-13 17:45:48 +02:00
12f6b884df
feat: add icon in open recent palette 2025-08-13 17:36:15 +02:00
5294ace5da
feat: add icon in file type palette 2025-08-13 17:35:42 +02:00
3f61e46dfe
feat: add icon in buffer palette 2025-08-13 17:35:22 +02:00
92b1354d4d
feat: add support for input box icons 2025-08-13 17:35:04 +02:00
4d2c7d8a8c
refactor: unify list pointer rendering 2025-08-13 17:34:38 +02:00
c50ab782ec
refactor: share file item menu rendering 2025-08-13 14:44:03 +02:00
f3296482d0
refactor: unify file icon rendering 2025-08-13 14:43:30 +02:00
38236bd93a
refactor: Buffer.Manager.get_buffer_for_file can be const 2025-08-13 14:40:59 +02:00
d07f0f5f35
feat: tweak home and palette styles 2025-08-13 14:03:42 +02:00
ea1ae2228e
fix: home menu rendering 2025-08-13 14:03:07 +02:00
b46e6edbca
fix: add padding to home menu length 2025-08-13 14:02:52 +02:00
bcfd17a0e2
feat: select widget styles based on widget type 2025-08-13 12:58:05 +02:00
fbc49c3dab
fix: home menu length 2025-08-13 12:54:00 +02:00
21f7b14970
refactor: remove widget debug output 2025-08-13 12:09:14 +02:00
5b852fdb3d
fix: prevent crash on invalid project directory 2025-08-13 12:09:06 +02:00
deee1afe13
feat: move widget styles to separate module
And add a few more border styles.
2025-08-13 11:47:49 +02:00
ea5843bc2c
fix: home screen menu padding 2025-08-13 01:46:38 +02:00
794f8be2be
feat: add more padding styles 2025-08-13 01:46:19 +02:00
d2c4fb66bd
fix: typo in WidgetList 2025-08-13 01:28:21 +02:00
b5a506450b
feat: add more boxed styles 2025-08-13 01:27:58 +02:00
d132df2d78
fix: use client_box correctly in filelist_view 2025-08-13 01:13:11 +02:00
c12f384a4f
feat: add thick_box border style 2025-08-13 01:12:15 +02:00
ae39016f03
fix: stop rendering widget list contents that are outside of it's box 2025-08-13 01:10:31 +02:00
83a0adccc7
feat: add menu border styles 2025-08-12 22:29:10 +02:00
ac2a7cfa83
feat: add flow mode global keybind for restart command 2025-08-12 14:06:42 +02:00
e7324dc110
feat: add flow mode keybind for set_session_tab_width command 2025-08-12 14:06:20 +02:00
3ed13a4ab8
fix: add missing arguments meta declartions on set_tab_width commands 2025-08-12 14:05:26 +02:00
21fe6103bf
feat: add open_most_recent_file command 2025-08-12 13:04:35 +02:00
80002e4d6b
feat: add set_buffer_tab_width and set_session_tab_width commands
Also, fold the tab_width and set_tab_width commands into one. The default
command (set_tab_width) now stores the tab_width in the persistent config.
2025-08-12 12:54:34 +02:00
4037d67fe9
feat: add support for session local tab_width setting 2025-08-12 12:53:45 +02:00
63a527726a
feat: add support for arguments to mini/numeric_input modes 2025-08-12 12:04:36 +02:00
1fcec1bab5
feat: add support for numeric arguments in cli exec calls 2025-08-12 12:02:41 +02:00
fb99aebfa9
feat: minor improvements to flow SELECT mode keybindings 2025-08-11 17:46:49 +02:00
3e0e75c9c8
feat: add interactive and non-interactive commands to set the current buffer's tab_width 2025-08-11 14:29:23 +02:00
1632061144
refactor: goto minimode into a reusable numeric input minimode 2025-08-11 14:07:11 +02:00
e886b7064a
fix: check that the second click in a double click is in the same cell 2025-08-10 21:53:36 +02:00
ab0a8f3c2c
feat: show file type icon in open_file completion 2025-08-09 22:20:17 +02:00
b913b8ad87
feat: write early log output to stderr/stdout until TUI is initialized 2025-08-09 19:15:01 +02:00
8789e8b89c
fix: use proper platform path separators for config files 2025-08-09 19:12:19 +02:00
de6ca62f6d
fix: allocate file_type_name in file_type_config cache 2025-08-09 18:17:15 +02:00
72d97f61f5
feat: display file icons in open_recent palette 2025-08-09 18:09:30 +02:00
ca33259ba4
feat: return file type and icon along with file names from the project manager 2025-08-09 18:06:49 +02:00
680c6f770e
refactor: use openFileAbsolute in mainview.read_restore_info 2025-08-09 18:06:12 +02:00
49 changed files with 1636 additions and 2530 deletions

View file

@ -17,11 +17,11 @@ pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Run unit tests");
const lint_step = b.step("lint", "Run lints");
var version = std.ArrayList(u8).init(b.allocator);
defer version.deinit();
gen_version(b, version.writer()) catch {
version.clearAndFree();
version.appendSlice("unknown") catch {};
var version: std.ArrayList(u8) = .empty;
defer version.deinit(b.allocator);
gen_version(b, version.writer(b.allocator)) catch {
version.clearAndFree(b.allocator);
version.appendSlice(b.allocator, "unknown") catch {};
};
return (if (release) &build_release else &build_development)(
@ -225,11 +225,11 @@ pub fn build_exe(
else => std.debug.panic("makeDir(\".cache/cdb\") failed: {any}", .{e}),
};
var version_info = std.ArrayList(u8).init(b.allocator);
defer version_info.deinit();
gen_version_info(b, target, version_info.writer(), optimize) catch {
version_info.clearAndFree();
version_info.appendSlice("unknown") catch {};
var version_info: std.ArrayList(u8) = .empty;
defer version_info.deinit(b.allocator);
gen_version_info(b, target, version_info.writer(b.allocator), optimize) catch {
version_info.clearAndFree(b.allocator);
version_info.appendSlice(b.allocator, "unknown") catch {};
};
const wf = b.addWriteFiles();
@ -687,17 +687,17 @@ fn gen_version_info(
const branch_ = try b.runAllowFail(&[_][]const u8{ "git", "rev-parse", "--abbrev-ref", "HEAD" }, &code, .Ignore);
const branch = std.mem.trimRight(u8, branch_, "\r\n ");
const tracking_branch_ = blk: {
var buf = std.ArrayList(u8).init(b.allocator);
defer buf.deinit();
try buf.appendSlice(branch);
try buf.appendSlice("@{upstream}");
var buf: std.ArrayList(u8) = .empty;
defer buf.deinit(b.allocator);
try buf.appendSlice(b.allocator, branch);
try buf.appendSlice(b.allocator, "@{upstream}");
break :blk (b.runAllowFail(&[_][]const u8{ "git", "rev-parse", "--abbrev-ref", buf.items }, &code, .Ignore) catch "");
};
const tracking_remote_name = if (std.mem.indexOfScalar(u8, tracking_branch_, '/')) |pos| tracking_branch_[0..pos] else "";
const tracking_remote_ = if (tracking_remote_name.len > 0) blk: {
var remote_config_path = std.ArrayList(u8).init(b.allocator);
defer remote_config_path.deinit();
try remote_config_path.writer().print("remote.{s}.url", .{tracking_remote_name});
var remote_config_path: std.ArrayList(u8) = .empty;
defer remote_config_path.deinit(b.allocator);
try remote_config_path.writer(b.allocator).print("remote.{s}.url", .{tracking_remote_name});
break :blk b.runAllowFail(&[_][]const u8{ "git", "config", remote_config_path.items }, &code, .Ignore) catch "(remote not found)";
} else "";
const remote_ = b.runAllowFail(&[_][]const u8{ "git", "config", "remote.origin.url" }, &code, .Ignore) catch "(origin not found)";

View file

@ -1 +1 @@
0.15.0-dev.1034+bd97b6618
0.15.1

View file

@ -5,18 +5,21 @@
.fingerprint = 0x52c0d670590aa80f,
.dependencies = .{
.syntax = .{ .path = "src/syntax" },
.syntax = .{
.url = "git+https://github.com/neurocyte/flow-syntax?ref=master#2c28091fe5ccfe66b304da0532b46f98fa1c28d3",
.hash = "flow_syntax-0.1.0-X8jOockOAQBBkI-6qpOw17CjdlrQeRhiFJFszv9U_THm",
},
.flags = .{
.url = "https://github.com/n0s4/flags/archive/372501d1576b5723829bcba98e41361132c7b618.tar.gz",
.hash = "flags-0.8.0-AAAAAJV0AACuGBBnpUnHqZzAhoGTp4ibFROBQQQZGRqx",
.url = "git+https://github.com/neurocyte/flags?ref=main#984b27948da3e4e40a253f76c85b51ec1a9ada11",
.hash = "flags-0.10.0-a_9h3gB4AADAYn0XaZuUqH1jKRmy6dqvAIbomtTIF6V1",
},
.dizzy = .{
.url = "https://github.com/neurocyte/dizzy/archive/c9219d23daccd9aa226cfde754fea278cb516459.tar.gz",
.hash = "dizzy-1.0.0-q40X4YCRAAAGYO9QOZiYYSOwiiFlqZlecMuQcxPiBcXM",
},
.thespian = .{
.url = "git+https://github.com/neurocyte/thespian?ref=master#0a386496cda74ef827d5770f6f071a7d2d54b91a",
.hash = "thespian-0.0.1-owFOjlgaBgCAOkRjH9_mDN7dnL8n8K3XA2hqqchjXZIk",
.url = "git+https://github.com/neurocyte/thespian?ref=master#8b2535db199d2215bea438abb323f3e3f6c58d99",
.hash = "thespian-0.0.1-owFOjjQbBgABzTPy7sWTQMJ7Ts5ielIYO3_6hfhxbQa_",
},
.themes = .{
.url = "https://github.com/neurocyte/flow-themes/releases/download/master-952f9f630ea9544088fd30293666ee0650b7a690/flow-themes.tar.gz",
@ -27,12 +30,12 @@
.hash = "fuzzig-0.1.1-Ji0xivxIAQBD0g8O_NV_0foqoPf3elsg9Sc3pNfdVH4D",
},
.vaxis = .{
.url = "https://github.com/neurocyte/libvaxis/archive/2a4137dadbe560b13b712fd3aa8a1c313fdd8c6e.tar.gz",
.hash = "vaxis-0.1.0-BWNV_KMOCQAe8oPD6cCn62Rg7oIVgF8FEWLpcAi7xDZQ",
.url = "git+https://github.com/neurocyte/libvaxis?ref=zig-0.15#851427cce25664626f5005322903c7398ecce781",
.hash = "vaxis-0.5.1-BWNV_B8YCQCzszczPof3gSnh1UJYBkGUouqR1m_NKbL3",
},
.zeit = .{
.url = "https://github.com/rockorager/zeit/archive/991f38266f86535e68431675e8feb84efa1f011b.tar.gz",
.hash = "zeit-0.6.0-5I6bk0t7AgCPM_cY1DoqJB2pnmG7MMtpdO5IxNpryJDy",
.url = "git+https://github.com/rockorager/zeit?ref=zig-0.15#ed2ca60db118414bda2b12df2039e33bad3b0b88",
.hash = "zeit-0.6.0-5I6bk0J9AgCVa0nnyL0lNY9Xa9F68hHq-ZarhuXNV-Jb",
},
.win32 = .{
.url = "https://github.com/marlersoft/zigwin32/archive/e8739b32a33ce48a3286aba31918b26a9dfc6ef0.tar.gz",

View file

@ -3,7 +3,6 @@ const tp = @import("thespian");
const cbor = @import("cbor");
const root = @import("root");
const tracy = @import("tracy");
const log = @import("log");
allocator: std.mem.Allocator,
pid: tp.pid,
@ -172,6 +171,7 @@ const Process = struct {
project: [:0]const u8,
sp_tag: [:0]const u8,
log_file: ?std.fs.File = null,
log_file_path: ?[]const u8 = null,
next_id: i32 = 0,
requests: std.StringHashMap(tp.pid),
state: enum { init, running } = .init,
@ -188,10 +188,8 @@ const Process = struct {
} else if (try cbor.match(cmd.buf, .{ tp.extract(&tag), tp.more })) {
//
} else {
const logger = log.logger("LSP");
defer logger.deinit();
var buf: [1024]u8 = undefined;
logger.print_err("create", "invalid command: {d} {s}", .{ cmd.buf.len, cmd.to_json(&buf) catch "{command too large}" });
send_msg(tp.self_pid().clone(), tag, .err, "invalid command: {d} {s}", .{ cmd.buf.len, cmd.to_json(&buf) catch "{command too large}" });
return error.InvalidLspCommand;
}
const self = try allocator.create(Process);
@ -227,6 +225,7 @@ const Process = struct {
self.close() catch {};
self.write_log("### terminated LSP process ###\n", .{});
if (self.log_file) |file| file.close();
if (self.log_file_path) |file_path| self.allocator.free(file_path);
}
fn close(self: *Process) error{CloseFailed}!void {
@ -245,6 +244,20 @@ const Process = struct {
}
}
fn msg(self: *const Process, comptime fmt: anytype, args: anytype) void {
send_msg(self.parent, self.tag, .msg, fmt, args);
}
fn err_msg(self: *const Process, comptime fmt: anytype, args: anytype) void {
send_msg(self.parent, self.tag, .err, fmt, args);
}
fn send_msg(proc: tp.pid, tag: []const u8, type_: enum { msg, err }, comptime fmt: anytype, args: anytype) void {
var buf: [@import("log").max_log_message]u8 = undefined;
const output = std.fmt.bufPrint(&buf, fmt, args) catch "MESSAGE TOO LARGE";
proc.send(.{ "lsp", type_, tag, output }) catch {};
}
fn start(self: *Process) tp.result {
const frame = tracy.initZone(@src(), .{ .name = module_name ++ " start" });
defer frame.deinit();
@ -255,8 +268,9 @@ const Process = struct {
var log_file_path = std.ArrayList(u8).init(self.allocator);
defer log_file_path.deinit();
const state_dir = root.get_state_dir() catch |e| return tp.exit_error(e, @errorReturnTrace());
log_file_path.writer().print("{s}/lsp-{s}.log", .{ state_dir, self.tag }) catch |e| return tp.exit_error(e, @errorReturnTrace());
log_file_path.writer().print("{s}{c}lsp-{s}.log", .{ state_dir, std.fs.path.sep, self.tag }) catch |e| return tp.exit_error(e, @errorReturnTrace());
self.log_file = std.fs.createFileAbsolute(log_file_path.items, .{ .truncate = true }) catch |e| return tp.exit_error(e, @errorReturnTrace());
self.log_file_path = log_file_path.toOwnedSlice() catch null;
}
fn receive(self: *Process, from: tp.pid_ref, m: tp.message) tp.result {
@ -440,18 +454,14 @@ const Process = struct {
}
fn handle_not_found(self: *Process) error{ExitNormal}!void {
const logger = log.logger("LSP");
defer logger.deinit();
logger.print_err("init", "'{s}' executable not found", .{self.tag});
self.err_msg("'{s}' executable not found", .{self.tag});
self.write_log("### '{s}' executable not found ###\n", .{self.tag});
self.parent.send(.{ sp_tag, self.tag, "not found" }) catch {};
return error.ExitNormal;
}
fn handle_terminated(self: *Process, err: []const u8, code: u32) error{ExitNormal}!void {
const logger = log.logger("LSP");
defer logger.deinit();
logger.print("terminated: {s} {d}", .{ err, code });
self.msg("terminated: {s} {d}", .{ err, code });
self.write_log("### subprocess terminated {s} {d} ###\n", .{ err, code });
self.parent.send(.{ sp_tag, self.tag, "done" }) catch {};
return error.ExitNormal;
@ -463,9 +473,9 @@ const Process = struct {
const id = self.next_id;
self.next_id += 1;
var msg = std.ArrayList(u8).init(self.allocator);
defer msg.deinit();
const msg_writer = msg.writer();
var request = std.ArrayList(u8).init(self.allocator);
defer request.deinit();
const msg_writer = request.writer();
try cbor.writeMapHeader(msg_writer, 4);
try cbor.writeValue(msg_writer, "jsonrpc");
try cbor.writeValue(msg_writer, "2.0");
@ -476,7 +486,7 @@ const Process = struct {
try cbor.writeValue(msg_writer, "params");
_ = try msg_writer.write(params_cb);
const json = try cbor.toJsonAlloc(self.allocator, msg.items);
const json = try cbor.toJsonAlloc(self.allocator, request.items);
defer self.allocator.free(json);
var output = std.ArrayList(u8).init(self.allocator);
defer output.deinit();
@ -499,9 +509,9 @@ const Process = struct {
fn send_response(self: *Process, cbor_id: []const u8, result_cb: []const u8) (error{Closed} || SendError || cbor.Error || cbor.JsonEncodeError)!void {
const sp = if (self.sp) |*sp| sp else return error.Closed;
var msg = std.ArrayList(u8).init(self.allocator);
defer msg.deinit();
const msg_writer = msg.writer();
var response = std.ArrayList(u8).init(self.allocator);
defer response.deinit();
const msg_writer = response.writer();
try cbor.writeMapHeader(msg_writer, 3);
try cbor.writeValue(msg_writer, "jsonrpc");
try cbor.writeValue(msg_writer, "2.0");
@ -510,7 +520,7 @@ const Process = struct {
try cbor.writeValue(msg_writer, "result");
_ = try msg_writer.write(result_cb);
const json = try cbor.toJsonAlloc(self.allocator, msg.items);
const json = try cbor.toJsonAlloc(self.allocator, response.items);
defer self.allocator.free(json);
var output = std.ArrayList(u8).init(self.allocator);
defer output.deinit();
@ -528,9 +538,9 @@ const Process = struct {
fn send_error_response(self: *Process, cbor_id: []const u8, error_code: ErrorCode, message: []const u8) (error{Closed} || SendError || cbor.Error || cbor.JsonEncodeError)!void {
const sp = if (self.sp) |*sp| sp else return error.Closed;
var msg = std.ArrayList(u8).init(self.allocator);
defer msg.deinit();
const msg_writer = msg.writer();
var response = std.ArrayList(u8).init(self.allocator);
defer response.deinit();
const msg_writer = response.writer();
try cbor.writeMapHeader(msg_writer, 3);
try cbor.writeValue(msg_writer, "jsonrpc");
try cbor.writeValue(msg_writer, "2.0");
@ -543,7 +553,7 @@ const Process = struct {
try cbor.writeValue(msg_writer, "message");
try cbor.writeValue(msg_writer, message);
const json = try cbor.toJsonAlloc(self.allocator, msg.items);
const json = try cbor.toJsonAlloc(self.allocator, response.items);
defer self.allocator.free(json);
var output = std.ArrayList(u8).init(self.allocator);
defer output.deinit();
@ -563,9 +573,9 @@ const Process = struct {
const have_params = !(cbor.match(params_cb, cbor.null_) catch false);
var msg = std.ArrayList(u8).init(self.allocator);
defer msg.deinit();
const msg_writer = msg.writer();
var notification = std.ArrayList(u8).init(self.allocator);
defer notification.deinit();
const msg_writer = notification.writer();
try cbor.writeMapHeader(msg_writer, 3);
try cbor.writeValue(msg_writer, "jsonrpc");
try cbor.writeValue(msg_writer, "2.0");
@ -578,7 +588,7 @@ const Process = struct {
try cbor.writeMapHeader(msg_writer, 0);
}
const json = try cbor.toJsonAlloc(self.allocator, msg.items);
const json = try cbor.toJsonAlloc(self.allocator, notification.items);
defer self.allocator.free(json);
var output = std.ArrayList(u8).init(self.allocator);
defer output.deinit();
@ -617,9 +627,9 @@ const Process = struct {
const json = if (params) |p| try cbor.toJsonPrettyAlloc(self.allocator, p) else null;
defer if (json) |p| self.allocator.free(p);
self.write_log("### RECV req: {s}\nmethod: {s}\n{s}\n###\n", .{ json_id, method, json orelse "no params" });
var msg = std.ArrayList(u8).init(self.allocator);
defer msg.deinit();
const writer = msg.writer();
var request = std.ArrayList(u8).init(self.allocator);
defer request.deinit();
const writer = request.writer();
try cbor.writeArrayHeader(writer, 7);
try cbor.writeValue(writer, sp_tag);
try cbor.writeValue(writer, self.project);
@ -628,7 +638,7 @@ const Process = struct {
try cbor.writeValue(writer, method);
try writer.writeAll(cbor_id);
if (params) |p| _ = try writer.write(p) else try cbor.writeValue(writer, null);
self.parent.send_raw(.{ .buf = msg.items }) catch return error.SendFailed;
self.parent.send_raw(.{ .buf = request.items }) catch return error.SendFailed;
}
fn receive_lsp_response(self: *Process, cbor_id: []const u8, result: ?[]const u8, err: ?[]const u8) Error!void {
@ -640,9 +650,9 @@ const Process = struct {
defer if (json_err) |p| self.allocator.free(p);
self.write_log("### RECV rsp: {s} {s}\n{s}\n###\n", .{ json_id, if (json_err) |_| "error" else "response", json_err orelse json orelse "no result" });
const from = self.requests.get(cbor_id) orelse return;
var msg = std.ArrayList(u8).init(self.allocator);
defer msg.deinit();
const writer = msg.writer();
var response = std.ArrayList(u8).init(self.allocator);
defer response.deinit();
const writer = response.writer();
try cbor.writeArrayHeader(writer, 4);
try cbor.writeValue(writer, sp_tag);
try cbor.writeValue(writer, self.tag);
@ -653,16 +663,16 @@ const Process = struct {
try cbor.writeValue(writer, "result");
_ = try writer.write(result_);
}
from.send_raw(.{ .buf = msg.items }) catch return error.SendFailed;
from.send_raw(.{ .buf = response.items }) catch return error.SendFailed;
}
fn receive_lsp_notification(self: *Process, method: []const u8, params: ?[]const u8) Error!void {
const json = if (params) |p| try cbor.toJsonPrettyAlloc(self.allocator, p) else null;
defer if (json) |p| self.allocator.free(p);
self.write_log("### RECV notify:\nmethod: {s}\n{s}\n###\n", .{ method, json orelse "no params" });
var msg = std.ArrayList(u8).init(self.allocator);
defer msg.deinit();
const writer = msg.writer();
var notification = std.ArrayList(u8).init(self.allocator);
defer notification.deinit();
const writer = notification.writer();
try cbor.writeArrayHeader(writer, 6);
try cbor.writeValue(writer, sp_tag);
try cbor.writeValue(writer, self.project);
@ -670,7 +680,7 @@ const Process = struct {
try cbor.writeValue(writer, "notify");
try cbor.writeValue(writer, method);
if (params) |p| _ = try writer.write(p) else try cbor.writeValue(writer, null);
self.parent.send_raw(.{ .buf = msg.items }) catch return error.SendFailed;
self.parent.send_raw(.{ .buf = notification.items }) catch return error.SendFailed;
}
fn write_log(self: *Process, comptime format: []const u8, args: anytype) void {

View file

@ -8,6 +8,7 @@ const Buffer = @import("Buffer");
const fuzzig = @import("fuzzig");
const tracy = @import("tracy");
const git = @import("git");
const file_type_config = @import("file_type_config");
const builtin = @import("builtin");
const LSP = @import("LSP.zig");
@ -52,6 +53,9 @@ pub const LspOrClientError = (LspError || ClientError);
const File = struct {
path: []const u8,
type: []const u8,
icon: []const u8,
color: u24,
mtime: i128,
pos: FilePos = .{},
visited: bool = false,
@ -341,7 +345,7 @@ pub fn request_n_most_recent_file(self: *Self, from: tp.pid_ref, n: usize) Clien
pub fn request_recent_files(self: *Self, from: tp.pid_ref, max: usize) ClientError!void {
defer from.send(.{ "PRJ", "recent_done", self.longest_file_path, "" }) catch {};
for (self.files.items, 0..) |file, i| {
from.send(.{ "PRJ", "recent", self.longest_file_path, file.path }) catch return error.ClientFailed;
from.send(.{ "PRJ", "recent", self.longest_file_path, file.path, file.type, file.icon, file.color }) catch return error.ClientFailed;
if (i >= max) return;
}
}
@ -356,7 +360,7 @@ fn simple_query_recent_files(self: *Self, from: tp.pid_ref, max: usize, query: [
defer self.allocator.free(matches);
var n: usize = 0;
while (n < query.len) : (n += 1) matches[n] = idx + n;
from.send(.{ "PRJ", "recent", self.longest_file_path, file.path, matches }) catch return error.ClientFailed;
from.send(.{ "PRJ", "recent", self.longest_file_path, file.path, file.type, file.icon, file.color, matches }) catch return error.ClientFailed;
i += 1;
if (i >= max) return i;
}
@ -379,6 +383,9 @@ pub fn query_recent_files(self: *Self, from: tp.pid_ref, max: usize, query: []co
const Match = struct {
path: []const u8,
type: []const u8,
icon: []const u8,
color: u24,
score: i32,
matches: []const usize,
};
@ -389,6 +396,9 @@ pub fn query_recent_files(self: *Self, from: tp.pid_ref, max: usize, query: []co
if (match.score) |score| {
(try matches.addOne()).* = .{
.path = file.path,
.type = file.type,
.icon = file.icon,
.color = file.color,
.score = score,
.matches = try self.allocator.dupe(usize, match.matches),
};
@ -404,13 +414,24 @@ pub fn query_recent_files(self: *Self, from: tp.pid_ref, max: usize, query: []co
std.mem.sort(Match, matches.items, {}, less_fn);
for (matches.items[0..@min(max, matches.items.len)]) |match|
from.send(.{ "PRJ", "recent", self.longest_file_path, match.path, match.matches }) catch return error.ClientFailed;
from.send(.{ "PRJ", "recent", self.longest_file_path, match.path, match.type, match.icon, match.color, match.matches }) catch return error.ClientFailed;
return @min(max, matches.items.len);
}
pub fn walk_tree_entry(self: *Self, file_path: []const u8, mtime: i128) OutOfMemoryError!void {
pub fn walk_tree_entry(
self: *Self,
file_path: []const u8,
mtime: i128,
) OutOfMemoryError!void {
const file_type: []const u8, const file_icon: []const u8, const file_color: u24 = guess_file_type(file_path);
self.longest_file_path = @max(self.longest_file_path, file_path.len);
(try self.pending.addOne(self.allocator)).* = .{ .path = try self.allocator.dupe(u8, file_path), .mtime = mtime };
(try self.pending.addOne(self.allocator)).* = .{
.path = try self.allocator.dupe(u8, file_path),
.type = file_type,
.icon = file_icon,
.color = file_color,
.mtime = mtime,
};
}
pub fn walk_tree_done(self: *Self, parent: tp.pid_ref) OutOfMemoryError!void {
@ -420,6 +441,35 @@ pub fn walk_tree_done(self: *Self, parent: tp.pid_ref) OutOfMemoryError!void {
return self.loaded(parent);
}
fn default_ft() struct { []const u8, []const u8, u24 } {
return .{
file_type_config.default.name,
file_type_config.default.icon,
file_type_config.default.color,
};
}
pub fn guess_path_file_type(path: []const u8, file_name: []const u8) struct { []const u8, []const u8, u24 } {
var buf: [4096]u8 = undefined;
const file_path = std.fmt.bufPrint(&buf, "{s}{}{s}", .{ path, std.fs.path.sep, file_name }) catch return default_ft();
return guess_file_type(file_path);
}
pub fn guess_file_type(file_path: []const u8) struct { []const u8, []const u8, u24 } {
var buf: [1024]u8 = undefined;
const content: []const u8 = blk: {
const file = std.fs.cwd().openFile(file_path, .{ .mode = .read_only }) catch break :blk &.{};
defer file.close();
const size = file.read(&buf) catch break :blk &.{};
break :blk buf[0..size];
};
return if (file_type_config.guess_file_type(file_path, content)) |ft| .{
ft.name,
ft.icon orelse file_type_config.default.icon,
ft.color orelse file_type_config.default.color,
} else default_ft();
}
fn merge_pending_files(self: *Self) OutOfMemoryError!void {
defer self.sort_files_by_mtime();
const existing = try self.files.toOwnedSlice(self.allocator);
@ -469,9 +519,13 @@ fn update_mru_internal(self: *Self, file_path: []const u8, mtime: i128, row: usi
}
return;
}
const file_type: []const u8, const file_icon: []const u8, const file_color: u24 = guess_file_type(file_path);
if (row != 0) {
(try self.files.addOne(self.allocator)).* = .{
.path = try self.allocator.dupe(u8, file_path),
.type = file_type,
.icon = file_icon,
.color = file_color,
.mtime = mtime,
.pos = .{ .row = row, .col = col },
.visited = true,
@ -479,6 +533,9 @@ fn update_mru_internal(self: *Self, file_path: []const u8, mtime: i128, row: usi
} else {
(try self.files.addOne(self.allocator)).* = .{
.path = try self.allocator.dupe(u8, file_path),
.type = file_type,
.icon = file_icon,
.color = file_color,
.mtime = mtime,
};
}
@ -1473,7 +1530,16 @@ fn read_position(position: []const u8) !Position {
return .{ .line = line.?, .character = character.? };
}
pub fn show_message(self: *Self, _: tp.pid_ref, params_cb: []const u8) !void {
pub fn show_message(self: *Self, params_cb: []const u8) !void {
return self.show_or_log_message(.show, params_cb);
}
pub fn log_message(self: *Self, params_cb: []const u8) !void {
return self.show_or_log_message(.log, params_cb);
}
fn show_or_log_message(self: *Self, operation: enum { show, log }, params_cb: []const u8) !void {
if (!tp.env.get().is("lsp_verbose")) return;
var type_: i32 = 0;
var message: ?[]const u8 = null;
var iter = params_cb;
@ -1493,7 +1559,14 @@ pub fn show_message(self: *Self, _: tp.pid_ref, params_cb: []const u8) !void {
if (type_ <= 2)
self.logger_lsp.err_msg("lsp", msg)
else
self.logger_lsp.print("{s}", .{msg});
self.logger_lsp.print("{s}: {s}", .{ @tagName(operation), msg });
}
pub fn show_notification(self: *Self, method: []const u8, params_cb: []const u8) !void {
if (!tp.env.get().is("lsp_verbose")) return;
const params = try cbor.toJsonAlloc(self.allocator, params_cb);
defer self.allocator.free(params);
self.logger_lsp.print("LSP notification: {s} -> {s}", .{ method, params });
}
pub fn register_capability(self: *Self, from: tp.pid_ref, cbor_id: []const u8, params_cb: []const u8) ClientError!void {
@ -1941,7 +2014,14 @@ pub fn process_git(self: *Self, parent: tp.pid_ref, m: tp.message) (OutOfMemoryE
} else if (try m.match(.{ tp.any, tp.any, "workspace_files", tp.extract(&path) })) {
self.longest_file_path = @max(self.longest_file_path, path.len);
const stat = std.fs.cwd().statFile(path) catch return;
(try self.pending.addOne(self.allocator)).* = .{ .path = try self.allocator.dupe(u8, path), .mtime = stat.mtime };
const file_type: []const u8, const file_icon: []const u8, const file_color: u24 = guess_file_type(path);
(try self.pending.addOne(self.allocator)).* = .{
.path = try self.allocator.dupe(u8, path),
.type = file_type,
.icon = file_icon,
.color = file_color,
.mtime = stat.mtime,
};
} else if (try m.match(.{ tp.any, tp.any, "workspace_files", tp.null_ })) {
self.state.workspace_files = .done;
try self.loaded(parent);

View file

@ -80,7 +80,7 @@ pub fn extract_state(self: *Self, iter: *[]const u8) !void {
}
}
pub fn get_buffer_for_file(self: *Self, file_path: []const u8) ?*Buffer {
pub fn get_buffer_for_file(self: *const Self, file_path: []const u8) ?*Buffer {
return self.buffers.get(file_path);
}

View file

@ -33,6 +33,7 @@ const Vtable = struct {
pub const Metadata = struct {
description: []const u8 = &[_]u8{},
arguments: []const ArgumentType = &[_]ArgumentType{},
icon: ?[]const u8 = null,
};
pub const ArgumentType = enum {
@ -188,6 +189,11 @@ pub fn get_arguments(id: ID) ?[]const ArgumentType {
return (commands.items[id] orelse return null).meta.arguments;
}
pub fn get_icon(id: ID) ?[]const u8 {
if (id >= commands.items.len) return null;
return (commands.items[id] orelse return null).meta.icon;
}
const suppressed_errors = std.StaticStringMap(void).initComptime(.{
.{ "enable_fast_scroll", void },
.{ "disable_fast_scroll", void },

View file

@ -36,6 +36,13 @@ show_fileicons: bool = true,
start_debugger_on_crash: bool = false,
widget_style: WidgetStyle = .compact,
palette_style: WidgetStyle = .bars_top_bottom,
panel_style: WidgetStyle = .compact,
home_style: WidgetStyle = .bars_top_bottom,
lsp_output: enum { quiet, verbose } = .quiet,
include_files: []const u8 = "",
pub const DigitStyle = enum {
@ -56,3 +63,25 @@ pub const IndentMode = enum {
spaces,
tabs,
};
pub const WidgetType = enum {
none,
palette,
panel,
home,
};
pub const WidgetStyle = enum {
bars_top_bottom,
bars_left_right,
thick_boxed,
extra_thick_boxed,
dotted_boxed,
rounded_boxed,
double_boxed,
single_double_top_bottom_boxed,
single_double_left_right_boxed,
boxed,
spacious,
compact,
};

View file

@ -20,6 +20,8 @@ pub const default = struct {
pub const color = 0x000000;
};
pub const folder_icon = "";
fn from_file_type(file_type: syntax.FileType) @This() {
return .{
.name = file_type.name,
@ -84,7 +86,7 @@ pub fn get(file_type_name: []const u8) !?@This() {
break :file_type if (syntax.FileType.get_by_name_static(file_type_name)) |ft| from_file_type(ft) else null;
}
};
try cache.put(cache_allocator, file_type_name, file_type);
try cache.put(cache_allocator, try cache_allocator.dupe(u8, file_type_name), file_type);
break :self file_type;
};
}

View file

@ -1,6 +1,7 @@
{
"project": {
"press": [
["ctrl+alt+shift+r", "restart"],
["ctrl+e", "find_file"],
["ctrl+shift+n", "create_new_file"],
["ctrl+r", "open_recent_project"],
@ -28,7 +29,8 @@
["f10", "theme_next"],
["f11", "toggle_panel"],
["f12", "toggle_inputview"],
["alt+!", "select_task"],
["alt+!", "run_task"],
["ctrl+1", "add_task"],
["ctrl+tab", "next_tab"],
["ctrl+shift+tab", "previous_tab"],
["ctrl+shift+e", "switch_buffers"],
@ -38,6 +40,7 @@
["f5", ["create_scratch_buffer", "*test*"], ["shell_execute_insert", "zig", "build", "test"]],
["f7", ["create_scratch_buffer", "*build*"], ["shell_execute_insert", "zig", "build"]],
["ctrl+f6", "open_version_info"],
["alt+shift+t", "set_session_tab_width"],
["alt+d", ["shell_execute_insert", "date", "--iso-8601"]],
["ctrl+alt+shift+d", ["shell_execute_insert", "date", "--iso-8601=seconds"]]
]
@ -162,6 +165,7 @@
["shift+f11", "toggle_highlight_columns"],
["ctrl+f11", "toggle_inspector_view"],
["f12", "goto_definition"],
["ctrl+.", "completion"],
["f34", "toggle_whitespace_mode"],
["escape", "cancel"],
["enter", "smart_insert_line"],
@ -236,6 +240,8 @@
["page_down", "select_page_down"],
["ctrl+page_up", "select_scroll_page_up"],
["ctrl+page_down", "select_scroll_page_down"],
["ctrl+b", "move_to_char", "select_to_char_left"],
["ctrl+t", "move_to_char", "select_to_char_right"],
["ctrl+space", "enter_mode", "normal"],
["ctrl+x", ["cut"], ["enter_mode", "normal"], ["cancel"]],
["ctrl+c", ["copy"], ["enter_mode", "normal"], ["cancel"]],
@ -249,6 +255,7 @@
"inherit": "project",
"on_match_failure": "ignore",
"press": [
["alt+f9", "home_next_widget_style"],
["ctrl+e", "find_file"],
["f", "find_file"],
["e", "find_file"],
@ -279,9 +286,12 @@
},
"overlay/palette": {
"press": [
["alt+f9", "overlay_next_widget_style"],
["alt+!", "add_task"],
["ctrl+j", "toggle_panel"],
["ctrl+q", "quit"],
["ctrl+w", "close_file"],
["ctrl+shift+f", "find_in_files"],
["ctrl+p", "palette_menu_up"],
["ctrl+n", "palette_menu_down"],
["ctrl+e", "palette_menu_down"],
@ -328,7 +338,7 @@
["right_control", "palette_menu_activate_quick"]
]
},
"mini/goto": {
"mini/numeric": {
"press": [
["ctrl+q", "quit"],
["ctrl+v", "system_paste"],
@ -338,7 +348,7 @@
["ctrl+l", "scroll_view_center_cycle"],
["ctrl+space", "mini_mode_cancel"],
["escape", "mini_mode_cancel"],
["enter", "exit_mini_mode"],
["enter", "mini_mode_select"],
["backspace", "mini_mode_delete_backwards"]
]
},
@ -347,6 +357,8 @@
["ctrl+g", "mini_mode_cancel"],
["ctrl+c", "mini_mode_cancel"],
["ctrl+l", "scroll_view_center_cycle"],
["tab", "mini_mode_insert_bytes", "\t"],
["enter", "mini_mode_insert_bytes", "\n"],
["escape", "mini_mode_cancel"],
["backspace", "mini_mode_cancel"]
]

View file

@ -11,6 +11,8 @@ subscriber: ?tp.pid,
heap: [32 + 1024]u8,
fba: std.heap.FixedBufferAllocator,
msg_store: MsgStore,
no_stdout: bool = false,
no_stderr: bool = false,
const MsgStore = std.DoublyLinkedList;
const MsgStoreEntry = struct {
@ -85,12 +87,23 @@ fn store_reset(self: *Self) void {
fn receive(self: *Self, from: tp.pid_ref, m: tp.message) tp.result {
errdefer self.deinit();
if (try m.match(.{ "log", tp.more })) {
var output: []const u8 = undefined;
if (try m.match(.{ "log", "error", tp.string, "std.log", "->", tp.extract(&output) })) {
if (self.subscriber) |subscriber| {
subscriber.send_raw(m) catch {};
} else {
self.store(m);
}
if (!self.no_stderr)
std.io.getStdErr().writer().print("{s}\n", .{output}) catch {};
} else if (try m.match(.{ "log", tp.string, tp.extract(&output) })) {
if (self.subscriber) |subscriber| {
subscriber.send_raw(m) catch {};
} else {
self.store(m);
}
if (!self.no_stdout)
std.io.getStdOut().writer().print("{s}\n", .{output}) catch {};
} else if (try m.match(.{"subscribe"})) {
// log("subscribed");
if (self.subscriber) |*s| s.deinit();
@ -101,6 +114,14 @@ fn receive(self: *Self, from: tp.pid_ref, m: tp.message) tp.result {
if (self.subscriber) |*s| s.deinit();
self.subscriber = null;
self.store_reset();
} else if (try m.match(.{ "stdout", "enable" })) {
self.no_stdout = false;
} else if (try m.match(.{ "stdout", "disable" })) {
self.no_stdout = true;
} else if (try m.match(.{ "stderr", "enable" })) {
self.no_stderr = false;
} else if (try m.match(.{ "stderr", "disable" })) {
self.no_stderr = true;
} else if (try m.match(.{"shutdown"})) {
return tp.exit_normal();
}
@ -208,6 +229,14 @@ pub fn unsubscribe() tp.result {
return tp.env.get().proc("log").send(.{"unsubscribe"});
}
pub fn stdout(state: enum { enable, disable }) void {
tp.env.get().proc("log").send(.{ "stdout", state }) catch {};
}
pub fn stderr(state: enum { enable, disable }) void {
tp.env.get().proc("log").send(.{ "stderr", state }) catch {};
}
var std_log_pid: ?tp.pid_ref = null;
pub fn set_std_log_pid(pid: ?tp.pid_ref) void {

View file

@ -6,6 +6,7 @@ const color = @import("color");
const flags = @import("flags");
const builtin = @import("builtin");
const bin_path = @import("bin_path");
const sep = std.fs.path.sep;
const list_languages = @import("list_languages.zig");
pub const file_link = @import("file_link.zig");
@ -330,7 +331,12 @@ pub fn main() anyerror!void {
try cbor.writeValue(writer, cmd_);
try cbor.writeArrayHeader(writer, count - 1);
while (cmd_args.next()) |arg| try cbor.writeValue(writer, arg);
while (cmd_args.next()) |arg| {
if (std.fmt.parseInt(isize, arg, 10) catch null) |i|
try cbor.writeValue(writer, i)
else
try cbor.writeValue(writer, arg);
}
try tui_proc.send_raw(.{ .buf = msg.items });
}
@ -398,7 +404,7 @@ fn trace_to_file(m: thespian.message.c_buffer_type) callconv(.C) void {
const a = std.heap.c_allocator;
var path = std.ArrayList(u8).init(a);
defer path.deinit();
path.writer().print("{s}/trace.log", .{get_state_dir() catch return}) catch return;
path.writer().print("{s}{c}trace.log", .{ get_state_dir() catch return, sep }) catch return;
const file = std.fs.createFileAbsolute(path.items, .{ .truncate = true }) catch return;
State.state = .{
.file = file,
@ -502,12 +508,12 @@ pub fn parse_text_config_file(T: type, allocator: std.mem.Allocator, conf: *T, b
lineno += 1;
if (line.len == 0 or line[0] == '#')
continue;
const sep = std.mem.indexOfScalar(u8, line, ' ') orelse {
const spc = std.mem.indexOfScalar(u8, line, ' ') orelse {
std.log.err("{s}:{}: {s} missing value", .{ file_name, lineno, line });
continue;
};
const name = line[0..sep];
const value_str = line[sep + 1 ..];
const name = line[0..spc];
const value_str = line[spc + 1 ..];
const cb = cbor.fromJsonAlloc(allocator, value_str) catch {
std.log.err("{s}:{}: {s} has bad value: {s}", .{ file_name, lineno, name, value_str });
continue;
@ -781,6 +787,11 @@ pub const ConfigDirError = error{
AppConfigDirUnavailable,
};
fn make_dir_error(path: []const u8, err: anytype) @TypeOf(err) {
std.log.err("failed to create directory: '{s}'", .{path});
return err;
}
fn get_app_config_dir(appname: []const u8) ConfigDirError![]const u8 {
const a = std.heap.c_allocator;
const local = struct {
@ -791,22 +802,22 @@ fn get_app_config_dir(appname: []const u8) ConfigDirError![]const u8 {
dir
else if (std.process.getEnvVarOwned(a, "XDG_CONFIG_HOME") catch null) |xdg| ret: {
defer a.free(xdg);
break :ret try std.fmt.bufPrint(&local.config_dir_buffer, "{s}/{s}", .{ xdg, appname });
break :ret try std.fmt.bufPrint(&local.config_dir_buffer, "{s}{c}{s}", .{ xdg, sep, appname });
} else if (std.process.getEnvVarOwned(a, "HOME") catch null) |home| ret: {
defer a.free(home);
const dir = try std.fmt.bufPrint(&local.config_dir_buffer, "{s}/.config", .{home});
const dir = try std.fmt.bufPrint(&local.config_dir_buffer, "{s}{c}.config", .{ home, sep });
std.fs.makeDirAbsolute(dir) catch |e| switch (e) {
error.PathAlreadyExists => {},
else => return error.MakeHomeConfigDirFailed,
else => return make_dir_error(dir, error.MakeHomeConfigDirFailed),
};
break :ret try std.fmt.bufPrint(&local.config_dir_buffer, "{s}/.config/{s}", .{ home, appname });
break :ret try std.fmt.bufPrint(&local.config_dir_buffer, "{s}{c}.config{c}{s}", .{ home, sep, sep, appname });
} else if (builtin.os.tag == .windows) ret: {
if (std.process.getEnvVarOwned(a, "APPDATA") catch null) |appdata| {
defer a.free(appdata);
const dir = try std.fmt.bufPrint(&local.config_dir_buffer, "{s}/{s}", .{ appdata, appname });
const dir = try std.fmt.bufPrint(&local.config_dir_buffer, "{s}{c}{s}", .{ appdata, sep, appname });
std.fs.makeDirAbsolute(dir) catch |e| switch (e) {
error.PathAlreadyExists => {},
else => return error.MakeAppConfigDirFailed,
else => return make_dir_error(dir, error.MakeAppConfigDirFailed),
};
break :ret dir;
} else return error.AppConfigDirUnavailable;
@ -815,14 +826,14 @@ fn get_app_config_dir(appname: []const u8) ConfigDirError![]const u8 {
local.config_dir = config_dir;
std.fs.makeDirAbsolute(config_dir) catch |e| switch (e) {
error.PathAlreadyExists => {},
else => return error.MakeConfigDirFailed,
else => return make_dir_error(config_dir, error.MakeConfigDirFailed),
};
var keybind_dir_buffer: [std.posix.PATH_MAX]u8 = undefined;
std.fs.makeDirAbsolute(try std.fmt.bufPrint(&keybind_dir_buffer, "{s}/{s}", .{ config_dir, keybind_dir })) catch {};
std.fs.makeDirAbsolute(try std.fmt.bufPrint(&keybind_dir_buffer, "{s}{c}{s}", .{ config_dir, sep, keybind_dir })) catch {};
var theme_dir_buffer: [std.posix.PATH_MAX]u8 = undefined;
std.fs.makeDirAbsolute(try std.fmt.bufPrint(&theme_dir_buffer, "{s}/{s}", .{ config_dir, theme_dir })) catch {};
std.fs.makeDirAbsolute(try std.fmt.bufPrint(&theme_dir_buffer, "{s}{c}{s}", .{ config_dir, sep, theme_dir })) catch {};
return config_dir;
}
@ -841,22 +852,22 @@ fn get_app_cache_dir(appname: []const u8) ![]const u8 {
dir
else if (std.process.getEnvVarOwned(a, "XDG_CACHE_HOME") catch null) |xdg| ret: {
defer a.free(xdg);
break :ret try std.fmt.bufPrint(&local.cache_dir_buffer, "{s}/{s}", .{ xdg, appname });
break :ret try std.fmt.bufPrint(&local.cache_dir_buffer, "{s}{c}{s}", .{ xdg, sep, appname });
} else if (std.process.getEnvVarOwned(a, "HOME") catch null) |home| ret: {
defer a.free(home);
const dir = try std.fmt.bufPrint(&local.cache_dir_buffer, "{s}/.cache", .{home});
const dir = try std.fmt.bufPrint(&local.cache_dir_buffer, "{s}{c}.cache", .{ home, sep });
std.fs.makeDirAbsolute(dir) catch |e| switch (e) {
error.PathAlreadyExists => {},
else => return e,
else => return make_dir_error(dir, e),
};
break :ret try std.fmt.bufPrint(&local.cache_dir_buffer, "{s}/.cache/{s}", .{ home, appname });
break :ret try std.fmt.bufPrint(&local.cache_dir_buffer, "{s}{c}.cache{c}{s}", .{ home, sep, sep, appname });
} else if (builtin.os.tag == .windows) ret: {
if (std.process.getEnvVarOwned(a, "APPDATA") catch null) |appdata| {
defer a.free(appdata);
const dir = try std.fmt.bufPrint(&local.cache_dir_buffer, "{s}/{s}", .{ appdata, appname });
const dir = try std.fmt.bufPrint(&local.cache_dir_buffer, "{s}{c}{s}", .{ appdata, sep, appname });
std.fs.makeDirAbsolute(dir) catch |e| switch (e) {
error.PathAlreadyExists => {},
else => return e,
else => return make_dir_error(dir, e),
};
break :ret dir;
} else return error.AppCacheDirUnavailable;
@ -865,7 +876,7 @@ fn get_app_cache_dir(appname: []const u8) ![]const u8 {
local.cache_dir = cache_dir;
std.fs.makeDirAbsolute(cache_dir) catch |e| switch (e) {
error.PathAlreadyExists => {},
else => return e,
else => return make_dir_error(cache_dir, e),
};
return cache_dir;
}
@ -884,27 +895,27 @@ fn get_app_state_dir(appname: []const u8) ![]const u8 {
dir
else if (std.process.getEnvVarOwned(a, "XDG_STATE_HOME") catch null) |xdg| ret: {
defer a.free(xdg);
break :ret try std.fmt.bufPrint(&local.state_dir_buffer, "{s}/{s}", .{ xdg, appname });
break :ret try std.fmt.bufPrint(&local.state_dir_buffer, "{s}{c}{s}", .{ xdg, sep, appname });
} else if (std.process.getEnvVarOwned(a, "HOME") catch null) |home| ret: {
defer a.free(home);
var dir = try std.fmt.bufPrint(&local.state_dir_buffer, "{s}/.local", .{home});
var dir = try std.fmt.bufPrint(&local.state_dir_buffer, "{s}{c}.local", .{ home, sep });
std.fs.makeDirAbsolute(dir) catch |e| switch (e) {
error.PathAlreadyExists => {},
else => return e,
else => return make_dir_error(dir, e),
};
dir = try std.fmt.bufPrint(&local.state_dir_buffer, "{s}/.local/state", .{home});
dir = try std.fmt.bufPrint(&local.state_dir_buffer, "{s}{c}.local{c}state", .{ home, sep, sep });
std.fs.makeDirAbsolute(dir) catch |e| switch (e) {
error.PathAlreadyExists => {},
else => return e,
else => return make_dir_error(dir, e),
};
break :ret try std.fmt.bufPrint(&local.state_dir_buffer, "{s}/.local/state/{s}", .{ home, appname });
break :ret try std.fmt.bufPrint(&local.state_dir_buffer, "{s}{c}.local{c}state{c}{s}", .{ home, sep, sep, sep, appname });
} else if (builtin.os.tag == .windows) ret: {
if (std.process.getEnvVarOwned(a, "APPDATA") catch null) |appdata| {
defer a.free(appdata);
const dir = try std.fmt.bufPrint(&local.state_dir_buffer, "{s}/{s}", .{ appdata, appname });
const dir = try std.fmt.bufPrint(&local.state_dir_buffer, "{s}{c}{s}", .{ appdata, sep, appname });
std.fs.makeDirAbsolute(dir) catch |e| switch (e) {
error.PathAlreadyExists => {},
else => return e,
else => return make_dir_error(dir, e),
};
break :ret dir;
} else return error.AppCacheDirUnavailable;
@ -913,7 +924,7 @@ fn get_app_state_dir(appname: []const u8) ![]const u8 {
local.state_dir = state_dir;
std.fs.makeDirAbsolute(state_dir) catch |e| switch (e) {
error.PathAlreadyExists => {},
else => return e,
else => return make_dir_error(state_dir, e),
};
return state_dir;
}
@ -926,7 +937,7 @@ fn get_app_config_dir_file_name(appname: []const u8, comptime config_file_name:
const local = struct {
var config_file_buffer: [std.posix.PATH_MAX]u8 = undefined;
};
return std.fmt.bufPrint(&local.config_file_buffer, "{s}/{s}", .{ try get_app_config_dir(appname), config_file_name });
return std.fmt.bufPrint(&local.config_file_buffer, "{s}{c}{s}", .{ try get_app_config_dir(appname), sep, config_file_name });
}
pub fn get_config_file_name(T: type) ![]const u8 {
@ -942,7 +953,7 @@ pub fn get_restore_file_name() ![]const u8 {
const restore_file = if (local.restore_file) |file|
file
else
try std.fmt.bufPrint(&local.restore_file_buffer, "{s}/{s}", .{ try get_app_state_dir(application_name), restore_file_name });
try std.fmt.bufPrint(&local.restore_file_buffer, "{s}{c}{s}", .{ try get_app_state_dir(application_name), sep, restore_file_name });
local.restore_file = restore_file;
return restore_file;
}
@ -958,7 +969,7 @@ fn get_keybind_namespaces_directory() ![]const u8 {
defer a.free(dir);
return try std.fmt.bufPrint(&local.dir_buffer, "{s}", .{dir});
}
return try std.fmt.bufPrint(&local.dir_buffer, "{s}/{s}", .{ try get_app_config_dir(application_name), keybind_dir });
return try std.fmt.bufPrint(&local.dir_buffer, "{s}{c}{s}", .{ try get_app_config_dir(application_name), sep, keybind_dir });
}
pub fn get_keybind_namespace_file_name(namespace_name: []const u8) ![]const u8 {
@ -966,7 +977,7 @@ pub fn get_keybind_namespace_file_name(namespace_name: []const u8) ![]const u8 {
const local = struct {
var file_buffer: [std.posix.PATH_MAX]u8 = undefined;
};
return try std.fmt.bufPrint(&local.file_buffer, "{s}/{s}.json", .{ dir, namespace_name });
return try std.fmt.bufPrint(&local.file_buffer, "{s}{c}{s}.json", .{ dir, sep, namespace_name });
}
const theme_dir = "themes";
@ -980,7 +991,7 @@ fn get_theme_directory() ![]const u8 {
defer a.free(dir);
return try std.fmt.bufPrint(&local.dir_buffer, "{s}", .{dir});
}
return try std.fmt.bufPrint(&local.dir_buffer, "{s}/{s}", .{ try get_app_config_dir(application_name), theme_dir });
return try std.fmt.bufPrint(&local.dir_buffer, "{s}{c}{s}", .{ try get_app_config_dir(application_name), sep, theme_dir });
}
pub fn get_theme_file_name(theme_name: []const u8) ![]const u8 {
@ -988,7 +999,7 @@ pub fn get_theme_file_name(theme_name: []const u8) ![]const u8 {
const local = struct {
var file_buffer: [std.posix.PATH_MAX]u8 = undefined;
};
return try std.fmt.bufPrint(&local.file_buffer, "{s}/{s}.json", .{ dir, theme_name });
return try std.fmt.bufPrint(&local.file_buffer, "{s}{c}{s}.json", .{ dir, sep, theme_name });
}
fn restart() noreturn {

View file

@ -27,7 +27,7 @@ const OutOfMemoryError = error{OutOfMemory};
const FileSystemError = error{FileSystem};
const SetCwdError = if (builtin.os.tag == .windows) error{UnrecognizedVolume} else error{};
const CallError = tp.CallError;
const ProjectManagerError = (SpawnError || error{ProjectManagerFailed});
const ProjectManagerError = (SpawnError || error{ ProjectManagerFailed, InvalidProjectDirectory });
pub fn get() SpawnError!Self {
const pid = tp.env.get().proc(module_name);
@ -63,6 +63,7 @@ pub fn open(rel_project_directory: []const u8) (ProjectManagerError || FileSyste
const project_directory = std.fs.cwd().realpath(rel_project_directory, &path_buf) catch "(none)";
const current_project = tp.env.get().str("project");
if (std.mem.eql(u8, current_project, project_directory)) return;
if (!root.is_directory(project_directory)) return error.InvalidProjectDirectory;
var dir = try std.fs.openDirAbsolute(project_directory, .{});
try dir.setAsCwd();
dir.close();
@ -97,9 +98,9 @@ pub fn request_recent_files(max: usize) (ProjectManagerError || ProjectError)!vo
return send(.{ "request_recent_files", project, max });
}
pub fn request_recent_projects(allocator: std.mem.Allocator) (ProjectError || CallError)!tp.message {
pub fn request_recent_projects() (ProjectManagerError || ProjectError)!void {
const project = tp.env.get().str("project");
return (try get()).pid.call(allocator, request_timeout, .{ "request_recent_projects", project });
return send(.{ "request_recent_projects", project });
}
pub fn query_recent_files(max: usize, query: []const u8) (ProjectManagerError || ProjectError)!void {
@ -332,6 +333,8 @@ const Process = struct {
var n: usize = 0;
var task: []const u8 = undefined;
var context: usize = undefined;
var tag: []const u8 = undefined;
var message: []const u8 = undefined;
var eol_mode: Buffer.EolModeTag = @intFromEnum(Buffer.EolMode.lf);
@ -404,6 +407,11 @@ const Process = struct {
self.hover(from, project_directory, path, row, col) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.ClientFailed;
} else if (try cbor.match(m.buf, .{ "get_mru_position", tp.extract(&project_directory), tp.extract(&path) })) {
self.get_mru_position(from, project_directory, path) catch |e| return from.forward_error(e, @errorReturnTrace()) catch error.ClientFailed;
} else if (try cbor.match(m.buf, .{ "lsp", "msg", tp.extract(&tag), tp.extract(&message) })) {
if (tp.env.get().is("lsp_verbose"))
self.logger.print("{s}: {s}", .{ tag, message });
} else if (try cbor.match(m.buf, .{ "lsp", "err", tp.extract(&tag), tp.extract(&message) })) {
self.logger.print("{s} error: {s}", .{ tag, message });
} else if (try cbor.match(m.buf, .{"shutdown"})) {
self.persist_projects();
from.send(.{ "project_manager", "shutdown" }) catch return error.ClientFailed;
@ -461,6 +469,9 @@ const Process = struct {
self.sort_projects_by_last_used(&recent_projects);
var message = std.ArrayList(u8).init(self.allocator);
const writer = message.writer();
try cbor.writeArrayHeader(writer, 3);
try cbor.writeValue(writer, "PRJ");
try cbor.writeValue(writer, "recent_projects");
try cbor.writeArrayHeader(writer, recent_projects.items.len);
for (recent_projects.items) |project| {
try cbor.writeArrayHeader(writer, 2);
@ -468,6 +479,7 @@ const Process = struct {
try cbor.writeValue(writer, if (self.projects.get(project.name)) |_| true else false);
}
from.send_raw(.{ .buf = message.items }) catch return error.ClientFailed;
self.logger.print("{d} projects found", .{recent_projects.items.len});
}
fn query_recent_files(self: *Process, from: tp.pid_ref, project_directory: []const u8, max: usize, query: []const u8) (ProjectError || Project.ClientError)!void {
@ -603,14 +615,11 @@ const Process = struct {
return if (std.mem.eql(u8, method, "textDocument/publishDiagnostics"))
project.publish_diagnostics(self.parent.ref(), params_cb)
else if (std.mem.eql(u8, method, "window/showMessage"))
project.show_message(self.parent.ref(), params_cb)
project.show_message(params_cb)
else if (std.mem.eql(u8, method, "window/logMessage"))
project.show_message(self.parent.ref(), params_cb)
else {
const params = try cbor.toJsonAlloc(self.allocator, params_cb);
defer self.allocator.free(params);
self.logger.print("LSP notification: {s} -> {s}", .{ method, params });
};
project.log_message(params_cb)
else
project.show_notification(method, params_cb);
}
fn dispatch_request(self: *Process, from: tp.pid_ref, project_directory: []const u8, language_server: []const u8, method: []const u8, cbor_id: []const u8, params_cb: []const u8) (ProjectError || Project.ClientError || cbor.Error || cbor.JsonEncodeError || UnsupportedError)!void {
@ -793,12 +802,19 @@ fn request_path_files_async(a_: std.mem.Allocator, parent_: tp.pid_ref, project_
var iter = self.dir.iterateAssumeFirstIteration();
errdefer |e| self.parent.send(.{ "PRJ", "path_error", self.project_name, self.path, e }) catch {};
while (try iter.next()) |entry| {
switch (entry.kind) {
.directory => try self.parent.send(.{ "PRJ", "path_entry", self.project_name, self.path, "DIR", entry.name }),
.sym_link => try self.parent.send(.{ "PRJ", "path_entry", self.project_name, self.path, "LINK", entry.name }),
.file => try self.parent.send(.{ "PRJ", "path_entry", self.project_name, self.path, "FILE", entry.name }),
const event_type = switch (entry.kind) {
.directory => "DIR",
.sym_link => "LINK",
.file => "FILE",
else => continue,
}
};
const default = file_type_config.default;
const file_type, const icon, const color = switch (entry.kind) {
.directory => .{ "directory", file_type_config.folder_icon, default.color },
.sym_link, .file => Project.guess_path_file_type(self.path, entry.name),
else => .{ default.name, default.icon, default.color },
};
try self.parent.send(.{ "PRJ", "path_entry", self.project_name, self.path, event_type, entry.name, file_type, icon, color });
count += 1;
if (count >= self.max) break;
}

View file

@ -323,6 +323,9 @@ pub fn process_renderer_event(self: *Self, msg: []const u8) Error!void {
})),
};
},
.mouse_leave => {
if (self.dispatch_event) |f| f(self.handler_ctx, try self.fmtmsg(.{"mouse_leave"}));
},
.focus_in => {
if (self.dispatch_event) |f| f(self.handler_ctx, try self.fmtmsg(.{"focus_in"}));
},

View file

@ -1 +0,0 @@
/.zig-cache/

View file

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2024 CJ van den Berg
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,2 +0,0 @@
# flow-syntax
Syntax highlighting module used by [flow](https://github.com/neurocyte/flow), [zat](https://github.com/neurocyte/zat) and [zine](https://github.com/kristoff-it/zine)

View file

@ -1,154 +0,0 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const use_tree_sitter = b.option(bool, "use_tree_sitter", "Enable tree-sitter (default: yes)") orelse true;
const options = b.addOptions();
options.addOption(bool, "use_tree_sitter", use_tree_sitter);
const options_mod = options.createModule();
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const tree_sitter_dep = b.dependency("tree_sitter", .{
.target = target,
.optimize = optimize,
});
const tree_sitter_host_dep = b.dependency("tree_sitter", .{});
const cbor_dep = b.dependency("cbor", .{
.target = target,
.optimize = optimize,
});
const ts_bin_query_gen = b.addExecutable(.{
.name = "ts_bin_query_gen",
.root_module = b.createModule(.{
.root_source_file = b.path("src/ts_bin_query_gen.zig"),
.target = b.graph.host,
.optimize = .Debug,
}),
});
ts_bin_query_gen.linkLibC();
ts_bin_query_gen.root_module.addImport("cbor", cbor_dep.module("cbor"));
ts_bin_query_gen.root_module.addImport("treez", tree_sitter_host_dep.module("treez"));
ts_bin_query_gen.root_module.addImport("build_options", options_mod);
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "queries/cmake/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-agda/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-astro/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-bash/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-c-sharp/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-c/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-cpp/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-css/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-diff/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-dockerfile/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-elixir/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-git-rebase/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-gitcommit/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-gleam/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-go/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-fish/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-haskell/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-hare/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-html/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-hurl/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-java/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-javascript/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-jsdoc/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-json/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-julia/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-kdl/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-lua/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-mail/queries/mail/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-make/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-markdown/tree-sitter-markdown/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-markdown/tree-sitter-markdown-inline/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-nasm/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-nim/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-ninja/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-nix/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-nu/queries/nu/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-ocaml/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-odin/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-openscad/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-org/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-php/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-powershell/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-proto/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-python/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-purescript/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-regex/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-rpmspec/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-ruby/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-rust/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-ssh-config/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-scala/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-scheme/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-superhtml/tree-sitter-superhtml/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-sql/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-swift/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-toml/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-typescript/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-typst/queries/typst/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-uxntal/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-vim/queries/vim/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-xml/queries/dtd/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-xml/queries/xml/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-yaml/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-zig/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-ziggy/tree-sitter-ziggy/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-ziggy/tree-sitter-ziggy-schema/queries/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "nvim-treesitter/queries/verilog/highlights.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "queries/cmake/injections.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-astro/queries/injections.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-cpp/queries/injections.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-elixir/queries/injections.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-gitcommit/queries/injections.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-hare/queries/injections.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-html/queries/injections.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-hurl/queries/injections.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-javascript/queries/injections.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-kdl/queries/injections.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-lua/queries/injections.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-markdown/tree-sitter-markdown-inline/queries/injections.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-markdown/tree-sitter-markdown/queries/injections.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-nasm/queries/injections.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-nix/queries/injections.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-nu/queries/nu/injections.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-odin/queries/injections.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-openscad/queries/injections.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-php/queries/injections.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-purescript/queries/injections.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-purescript/vim_queries/injections.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-rust/queries/injections.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-superhtml/tree-sitter-superhtml/queries/injections.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-swift/queries/injections.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-typst/queries/typst/injections.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-uxntal/queries/injections.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-vim/queries/vim/injections.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "tree-sitter-zig/queries/injections.scm");
ts_queryfile(b, tree_sitter_dep, ts_bin_query_gen, "nvim-treesitter/queries/verilog/injections.scm");
const syntax_mod = b.addModule("syntax", .{
.root_source_file = b.path("src/syntax.zig"),
.imports = &.{
.{ .name = "build_options", .module = options_mod },
.{ .name = "cbor", .module = cbor_dep.module("cbor") },
.{ .name = "treez", .module = tree_sitter_dep.module("treez") },
},
});
if (use_tree_sitter) {
const ts_bin_query_gen_step = b.addRunArtifact(ts_bin_query_gen);
const output = ts_bin_query_gen_step.addOutputFileArg("bin_queries.cbor");
syntax_mod.addAnonymousImport("syntax_bin_queries", .{ .root_source_file = output });
}
}
fn ts_queryfile(b: *std.Build, dep: *std.Build.Dependency, bin_gen: *std.Build.Step.Compile, comptime sub_path: []const u8) void {
const module = b.createModule(.{ .root_source_file = dep.path(sub_path) });
bin_gen.root_module.addImport(sub_path, module);
}

View file

@ -1,22 +0,0 @@
.{
.name = .flow_syntax,
.version = "0.1.0",
.fingerprint = 0x3ba2584ea1cec85f,
.minimum_zig_version = "0.15.0-dev.1048+f43f89a70",
.dependencies = .{
.tree_sitter = .{
.url = "https://github.com/neurocyte/tree-sitter/releases/download/master-f1f032d24f621e2ee4deab1c424d3bf9fb809f6e/source.tar.gz",
.hash = "tree_sitter-0.22.4-150-g7e3f5726-z0LhyN88UicDHlr22vQnOZ3DW9NWN1gOhDwLuCRXvrh2",
},
.cbor = .{
.url = "git+https://github.com/neurocyte/cbor#6eccce0b984296e7d05c20d83933cb31530e4fac",
.hash = "cbor-1.0.0-RcQE_N3yAADXjbyvhsmTQ6lf22l1nYgePq5FT8NaC4ic",
},
},
.paths = .{
"src",
"build.zig",
"build.zig.zon",
},
}

View file

@ -1,195 +0,0 @@
const std = @import("std");
const build_options = @import("build_options");
const treez = if (build_options.use_tree_sitter)
@import("treez")
else
@import("treez_dummy.zig");
const Self = @This();
pub const tss = @import("ts_serializer.zig");
pub const FileType = @import("file_type.zig");
const Query = treez.Query;
allocator: std.mem.Allocator,
mutex: ?std.Thread.Mutex,
highlights: std.StringHashMapUnmanaged(*CacheEntry) = .{},
injections: std.StringHashMapUnmanaged(*CacheEntry) = .{},
errors: std.StringHashMapUnmanaged(*CacheEntry) = .{},
ref_count: usize = 1,
const CacheEntry = struct {
mutex: ?std.Thread.Mutex,
query: ?*Query,
query_arena: ?*std.heap.ArenaAllocator,
query_type: QueryType,
file_type_name: []const u8,
lang_fn: FileType.LangFn,
fn destroy(self: *@This(), allocator: std.mem.Allocator) void {
if (self.query_arena) |a| {
a.deinit();
allocator.destroy(a);
} else if (self.query) |q|
q.destroy();
self.query_arena = null;
self.query = null;
}
};
pub const QueryType = enum {
highlights,
errors,
injections,
};
const QueryParseError = error{
InvalidSyntax,
InvalidNodeType,
InvalidField,
InvalidCapture,
InvalidStructure,
InvalidLanguage,
};
const CacheError = error{
NotFound,
OutOfMemory,
};
pub const Error = CacheError || QueryParseError || QuerySerializeError;
pub fn create(allocator: std.mem.Allocator, opts: struct { lock: bool = false }) !*Self {
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = .{
.allocator = allocator,
.mutex = if (opts.lock) .{} else null,
};
return self;
}
pub fn deinit(self: *Self) void {
self.release_ref_unlocked_and_maybe_destroy();
}
fn add_ref_locked(self: *Self) void {
std.debug.assert(self.ref_count > 0);
self.ref_count += 1;
}
fn release_ref_unlocked_and_maybe_destroy(self: *Self) void {
{
if (self.mutex) |*mtx| mtx.lock();
defer if (self.mutex) |*mtx| mtx.unlock();
self.ref_count -= 1;
if (self.ref_count > 0) return;
}
release_cache_entry_hash_map(self.allocator, &self.highlights);
release_cache_entry_hash_map(self.allocator, &self.errors);
release_cache_entry_hash_map(self.allocator, &self.injections);
self.allocator.destroy(self);
}
fn release_cache_entry_hash_map(allocator: std.mem.Allocator, hash_map: *std.StringHashMapUnmanaged(*CacheEntry)) void {
var iter = hash_map.iterator();
while (iter.next()) |p| {
allocator.free(p.key_ptr.*);
p.value_ptr.*.destroy(allocator);
allocator.destroy(p.value_ptr.*);
}
hash_map.deinit(allocator);
}
fn get_cache_entry(self: *Self, file_type: FileType, comptime query_type: QueryType) CacheError!*CacheEntry {
if (self.mutex) |*mtx| mtx.lock();
defer if (self.mutex) |*mtx| mtx.unlock();
const hash = switch (query_type) {
.highlights => &self.highlights,
.errors => &self.errors,
.injections => &self.injections,
};
return if (hash.get(file_type.name)) |entry| entry else blk: {
const entry_ = try hash.getOrPut(self.allocator, try self.allocator.dupe(u8, file_type.name));
const q = try self.allocator.create(CacheEntry);
q.* = .{
.query = null,
.query_arena = null,
.mutex = if (self.mutex) |_| .{} else null,
.lang_fn = file_type.lang_fn,
.file_type_name = file_type.name,
.query_type = query_type,
};
entry_.value_ptr.* = q;
break :blk q;
};
}
fn get_cached_query(self: *Self, entry: *CacheEntry) Error!?*Query {
if (entry.mutex) |*mtx| mtx.lock();
defer if (entry.mutex) |*mtx| mtx.unlock();
return if (entry.query) |query| query else blk: {
const lang = entry.lang_fn() orelse std.debug.panic("tree-sitter parser function failed for language: {s}", .{entry.file_type_name});
const queries = FileType.queries.get(entry.file_type_name) orelse return null;
const query_bin = switch (entry.query_type) {
.highlights => queries.highlights_bin,
.errors => queries.errors_bin,
.injections => queries.injections_bin orelse return null,
};
const query, const arena = try deserialize_query(query_bin, lang, self.allocator);
entry.query = query;
entry.query_arena = arena;
break :blk entry.query.?;
};
}
fn pre_load_internal(self: *Self, file_type: *const FileType, comptime query_type: QueryType) Error!void {
_ = try self.get_cached_query(try self.get_cache_entry(file_type, query_type));
}
pub fn pre_load(self: *Self, lang_name: []const u8) Error!void {
const file_type = FileType.get_by_name(lang_name) orelse return;
_ = try self.pre_load_internal(file_type, .highlights);
_ = try self.pre_load_internal(file_type, .errors);
_ = try self.pre_load_internal(file_type, .injections);
}
fn ReturnType(comptime query_type: QueryType) type {
return switch (query_type) {
.highlights => *Query,
.errors => *Query,
.injections => ?*Query,
};
}
pub fn get(self: *Self, file_type: FileType, comptime query_type: QueryType) Error!ReturnType(query_type) {
const query = try self.get_cached_query(try self.get_cache_entry(file_type, query_type));
self.add_ref_locked();
return switch (@typeInfo(ReturnType(query_type))) {
.optional => |_| query,
else => query.?,
};
}
pub fn release(self: *Self, query: *Query, comptime query_type: QueryType) void {
_ = query;
_ = query_type;
self.release_ref_unlocked_and_maybe_destroy();
}
pub const QuerySerializeError = (tss.SerializeError || tss.DeserializeError);
fn deserialize_query(query_bin: []const u8, language: ?*const treez.Language, allocator: std.mem.Allocator) QuerySerializeError!struct { *Query, *std.heap.ArenaAllocator } {
var ts_query_out, const arena = try tss.fromCbor(query_bin, allocator);
ts_query_out.language = @intFromPtr(language);
const query_out: *Query = @alignCast(@ptrCast(ts_query_out));
return .{ query_out, arena };
}

View file

@ -1,207 +0,0 @@
const std = @import("std");
const cbor = @import("cbor");
const build_options = @import("build_options");
const treez = if (build_options.use_tree_sitter)
@import("treez")
else
@import("treez_dummy.zig");
pub const FileType = @This();
color: u24,
icon: []const u8,
name: []const u8,
description: []const u8,
lang_fn: LangFn,
extensions: []const []const u8,
first_line_matches: ?FirstLineMatch = null,
comment: []const u8,
formatter: ?[]const []const u8,
language_server: ?[]const []const u8,
pub fn get_by_name_static(name: []const u8) ?FileType {
return FileType.static_file_types.get(name);
}
pub fn get_all() []const FileType {
return FileType.static_file_types.values();
}
pub fn guess_static(file_path: ?[]const u8, content: []const u8) ?FileType {
if (guess_first_line_static(content)) |ft| return ft;
for (static_file_types.values()) |file_type|
if (file_path) |fp| if (match_file_type(file_type.extensions, fp))
return file_type;
return null;
}
fn guess_first_line_static(content: []const u8) ?FileType {
const first_line = if (std.mem.indexOf(u8, content, "\n")) |pos| content[0..pos] else content;
for (static_file_types.values()) |file_type|
if (file_type.first_line_matches) |match|
if (match_first_line(match.prefix, match.content, first_line))
return file_type;
return null;
}
pub fn match_first_line(match_prefix: ?[]const u8, match_content: ?[]const u8, first_line: []const u8) bool {
if (match_prefix == null and match_content == null) return false;
if (match_prefix) |prefix|
if (prefix.len > first_line.len or !std.mem.eql(u8, first_line[0..prefix.len], prefix))
return false;
if (match_content) |content|
if (std.mem.indexOf(u8, first_line, content)) |_| {} else return false;
return true;
}
pub fn match_file_type(extensions: []const []const u8, file_path: []const u8) bool {
const basename = std.fs.path.basename(file_path);
const extension = std.fs.path.extension(file_path);
return for (extensions) |ext| {
if (ext.len == basename.len and std.mem.eql(u8, ext, basename))
return true;
if (extension.len > 0 and ext.len == extension.len - 1 and std.mem.eql(u8, ext, extension[1..]))
return true;
} else false;
}
pub fn Parser(comptime lang: []const u8) LangFn {
return get_parser(lang);
}
fn get_parser(comptime lang: []const u8) LangFn {
if (build_options.use_tree_sitter) {
const language_name = ft_func_name(lang);
return @extern(?LangFn, .{ .name = "tree_sitter_" ++ language_name }) orelse @compileError(std.fmt.comptimePrint("Cannot find extern tree_sitter_{s}", .{language_name}));
} else {
return treez.Language.LangFn;
}
}
fn ft_func_name(comptime lang: []const u8) []const u8 {
var transform: [lang.len]u8 = undefined;
for (lang, 0..) |c, i|
transform[i] = if (c == '-') '_' else c;
const func_name = transform;
return &func_name;
}
pub const LangFn = *const fn () callconv(.c) ?*const treez.Language;
pub const FirstLineMatch = struct {
prefix: ?[]const u8 = null,
content: ?[]const u8 = null,
};
const static_file_type_list = load_file_types(@import("file_types.zig"));
const static_file_types = std.StaticStringMap(FileType).initComptime(static_file_type_list);
fn vec(comptime args: anytype) []const []const u8 {
var cmd: []const []const u8 = &[_][]const u8{};
inline for (args) |arg| {
cmd = cmd ++ [_][]const u8{arg};
}
return cmd;
}
const ListEntry = struct { []const u8, FileType };
fn load_file_types(comptime Namespace: type) []const ListEntry {
comptime switch (@typeInfo(Namespace)) {
.@"struct" => |info| {
var count = 0;
for (info.decls) |_| {
// @compileLog(decl.name, @TypeOf(@field(Namespace, decl.name)));
count += 1;
}
var construct_types: [count]ListEntry = undefined;
var i = 0;
for (info.decls) |decl| {
const lang = decl.name;
const args = @field(Namespace, lang);
construct_types[i] = .{ lang, .{
.color = if (@hasField(@TypeOf(args), "color")) args.color else 0xffffff,
.icon = if (@hasField(@TypeOf(args), "icon")) args.icon else "󱀫",
.name = lang,
.description = args.description,
.lang_fn = if (@hasField(@TypeOf(args), "parser")) args.parser else get_parser(lang),
.extensions = vec(args.extensions),
.comment = args.comment,
.first_line_matches = if (@hasField(@TypeOf(args), "first_line_matches")) args.first_line_matches else null,
.formatter = if (@hasField(@TypeOf(args), "formatter")) vec(args.formatter) else null,
.language_server = if (@hasField(@TypeOf(args), "language_server")) vec(args.language_server) else null,
} };
i += 1;
}
const types = construct_types;
return &types;
},
else => @compileError("expected tuple or struct type"),
};
}
pub const FileTypeQueries = struct {
highlights_bin: []const u8,
errors_bin: []const u8,
injections_bin: ?[]const u8,
};
pub const queries = std.StaticStringMap(FileTypeQueries).initComptime(load_queries());
fn load_queries() []const struct { []const u8, FileTypeQueries } {
if (!build_options.use_tree_sitter) return &.{};
@setEvalBranchQuota(32000);
const queries_cb = @embedFile("syntax_bin_queries");
var iter: []const u8 = queries_cb;
var len = cbor.decodeMapHeader(&iter) catch |e| {
@compileLog("cbor.decodeMapHeader", e);
@compileError("invalid syntax_bin_queries");
};
var construct_types: [len]struct { []const u8, FileTypeQueries } = undefined;
var i = 0;
while (len > 0) : (len -= 1) {
var lang: []const u8 = undefined;
if (!try cbor.matchString(&iter, &lang))
@compileError("invalid language name field");
construct_types[i] = .{ lang, .{
.highlights_bin = blk: {
var iter_: []const u8 = iter;
break :blk get_query_value_bin(&iter_, "highlights") orelse @compileError("missing highlights for " ++ lang);
},
.errors_bin = blk: {
var iter_: []const u8 = iter;
break :blk get_query_value_bin(&iter_, "errors") orelse @compileError("missing errors query for " ++ lang);
},
.injections_bin = blk: {
var iter_: []const u8 = iter;
break :blk get_query_value_bin(&iter_, "injections");
},
} };
try cbor.skipValue(&iter);
i += 1;
}
const types = construct_types;
return &types;
}
fn get_query_value_bin(iter: *[]const u8, comptime query: []const u8) ?[]const u8 {
var len = cbor.decodeMapHeader(iter) catch |e| {
@compileLog("cbor.decodeMapHeader", e);
@compileError("invalid query map in syntax_bin_queries");
};
while (len > 0) : (len -= 1) {
var query_name: []const u8 = undefined;
if (!try cbor.matchString(iter, &query_name))
@compileError("invalid query name field");
if (std.mem.eql(u8, query_name, query)) {
var query_value: []const u8 = undefined;
if (try cbor.matchValue(iter, cbor.extract(&query_value)))
return query_value;
@compileError("invalid query value field");
} else {
try cbor.skipValue(iter);
}
}
return null;
}

View file

@ -1,609 +0,0 @@
const file_type = @import("file_type.zig");
const FirstLineMatch = file_type.FirstLineMatch;
pub const agda = .{
.description = "Agda",
.extensions = .{"agda"},
.comment = "--",
};
pub const astro = .{
.description = "Astro",
.icon = "",
.extensions = .{"astro"},
.comment = "//",
.language_server = .{ "astro-ls", "--stdio" },
};
pub const bash = .{
.description = "Bash",
.color = 0x3e474a,
.icon = "󱆃",
.extensions = .{ "sh", "bash", ".profile" },
.comment = "#",
.first_line_matches = FirstLineMatch{ .prefix = "#!", .content = "sh" },
.formatter = .{ "shfmt", "--indent", "4" },
.language_server = .{ "bash-language-server", "start" },
};
pub const c = .{
.description = "C",
.icon = "",
.extensions = .{"c"},
.comment = "//",
.formatter = .{"clang-format"},
.language_server = .{"clangd"},
};
pub const @"c-sharp" = .{
.description = "C#",
.color = 0x68217a,
.icon = "󰌛",
.extensions = .{"cs"},
.comment = "//",
.language_server = .{ "omnisharp", "-lsp" },
.formatter = .{ "csharpier", "format" },
};
pub const conf = .{
.description = "Config",
.color = 0x000000,
.icon = "",
.extensions = .{ "conf", "log", "config", ".gitconfig", "gui_config" },
.highlights = fish.highlights,
.comment = "#",
.parser = fish.parser,
};
pub const cmake = .{
.description = "CMake",
.color = 0x004078,
.icon = "",
.extensions = .{ "CMakeLists.txt", "cmake", "cmake.in" },
.comment = "#",
.highlights = "queries/cmake/highlights.scm",
.injections = "queries/cmake/injections.scm",
.formatter = .{"cmake-format"},
.language_server = .{"cmake-language-server"},
};
pub const cpp = .{
.description = "C++",
.color = 0x9c033a,
.icon = "",
.extensions = .{ "cc", "cpp", "cxx", "hpp", "hxx", "h", "ipp", "ixx" },
.comment = "//",
.highlights_list = .{
"tree-sitter-c/queries/highlights.scm",
"tree-sitter-cpp/queries/highlights.scm",
},
.injections = "tree-sitter-cpp/queries/injections.scm",
.formatter = .{"clang-format"},
.language_server = .{"clangd"},
};
pub const css = .{
.description = "CSS",
.color = 0x3d8fc6,
.icon = "󰌜",
.extensions = .{"css"},
.comment = "//",
.language_server = .{ "vscode-css-language-server", "--stdio" },
};
pub const diff = .{
.description = "Diff",
.extensions = .{ "diff", "patch", "rej" },
.comment = "#",
};
pub const dockerfile = .{
.description = "Docker",
.color = 0x019bc6,
.icon = "",
.extensions = .{ "Dockerfile", "dockerfile", "docker", "Containerfile", "container" },
.comment = "#",
};
pub const dtd = .{
.description = "DTD",
.icon = "󰗀",
.extensions = .{"dtd"},
.comment = "<!--",
.highlights = "tree-sitter-xml/queries/dtd/highlights.scm",
};
pub const elixir = .{
.description = "Elixir",
.color = 0x4e2a8e,
.icon = "",
.extensions = .{ "ex", "exs" },
.comment = "#",
.injections = "tree-sitter-elixir/queries/injections.scm",
.formatter = .{ "mix", "format", "-" },
.language_server = .{"elixir-ls"},
};
pub const fish = .{
.description = "Fish",
.extensions = .{"fish"},
.comment = "#",
.parser = @import("file_type.zig").Parser("fish"),
.highlights = "tree-sitter-fish/queries/highlights.scm",
};
pub const @"git-rebase" = .{
.description = "Git (rebase)",
.color = 0xf34f29,
.icon = "",
.extensions = .{"git-rebase-todo"},
.comment = "#",
};
pub const gitcommit = .{
.description = "Git (commit)",
.color = 0xf34f29,
.icon = "",
.extensions = .{"COMMIT_EDITMSG"},
.comment = "#",
.injections = "tree-sitter-gitcommit/queries/injections.scm",
};
pub const gleam = .{
.description = "Gleam",
.color = 0xffaff3,
.icon = "󰦥",
.extensions = .{"gleam"},
.comment = "//",
.language_server = .{ "gleam", "lsp" },
.formatter = .{ "gleam", "format", "--stdin" },
};
pub const go = .{
.description = "Go",
.color = 0x00acd7,
.icon = "󰟓",
.extensions = .{"go"},
.comment = "//",
.language_server = .{"gopls"},
.formatter = .{"gofmt"},
};
pub const hare = .{
.description = "Hare",
.extensions = .{"ha"},
.comment = "//",
};
pub const haskell = .{
.description = "Haskell",
.color = 0x5E5185,
.icon = "󰲒",
.extensions = .{"hs"},
.comment = "--",
.language_server = .{ "haskell-language-server-wrapper", "lsp" },
};
pub const html = .{
.description = "HTML",
.color = 0xe54d26,
.icon = "󰌝",
.extensions = .{"html"},
.comment = "<!--",
.injections = "tree-sitter-html/queries/injections.scm",
.language_server = .{ "superhtml", "lsp" }, // https://github.com/kristoff-it/super-html.git
.formatter = .{ "superhtml", "fmt", "--stdin" },
};
pub const superhtml = .{
.description = "SuperHTML",
.color = 0xe54d26,
.icon = "󰌝",
.extensions = .{"shtml"},
.comment = "<!--",
.highlights = "tree-sitter-superhtml/tree-sitter-superhtml/queries/highlights.scm",
.injections = "tree-sitter-superhtml/tree-sitter-superhtml/queries/injections.scm",
.language_server = .{ "superhtml", "lsp" },
.formatter = .{ "superhtml", "fmt", "--stdin-super" },
};
pub const hurl = .{
.description = "Hurl",
.color = 0xff0087,
.icon = "",
.extensions = .{"hurl"},
.comment = "#",
.injections = "tree-sitter-hurl/queries/injections.scm",
};
pub const java = .{
.description = "Java",
.color = 0xEA2D2E,
.icon = "",
.extensions = .{"java"},
.comment = "//",
};
pub const javascript = .{
.description = "JavaScript",
.color = 0xf0db4f,
.icon = "󰌞",
.extensions = .{"js"},
.comment = "//",
.injections = "tree-sitter-javascript/queries/injections.scm",
.language_server = .{ "typescript-language-server", "--stdio" },
.formatter = .{ "prettier", "--parser", "typescript" },
};
pub const json = .{
.description = "JSON",
.extensions = .{"json"},
.comment = "//",
.language_server = .{ "vscode-json-language-server", "--stdio" },
.formatter = .{ "prettier", "--parser", "json" },
};
pub const julia = .{
.description = "Julia",
.color = 0x4D64AE,
.icon = "",
.extensions = .{"jl"},
.comment = "#",
.language_server = .{ "julia", "-e", "using LanguageServer; runserver()" },
.formatter = .{ "julia", "-e", "using JuliaFormatter; print(format_text(read(stdin, String)))" },
};
pub const kdl = .{
.description = "KDL",
.color = 0x000000,
.icon = "",
.extensions = .{"kdl"},
.comment = "//",
};
pub const lua = .{
.description = "Lua",
.color = 0x02027d,
.icon = "󰢱",
.extensions = .{"lua"},
.comment = "--",
.injections = "tree-sitter-lua/queries/injections.scm",
.first_line_matches = FirstLineMatch{ .prefix = "--", .content = "lua" },
.language_server = .{"lua-lsp"},
};
pub const mail = .{
.description = "E-Mail",
.icon = "󰇮",
.extensions = .{ "eml", "mbox" },
.comment = ">",
.highlights = "tree-sitter-mail/queries/mail/highlights.scm",
.first_line_matches = FirstLineMatch{ .prefix = "From" },
};
pub const make = .{
.description = "Make",
.extensions = .{ "makefile", "Makefile", "MAKEFILE", "GNUmakefile", "mk", "mak", "dsp" },
.comment = "#",
};
pub const markdown = .{
.description = "Markdown",
.color = 0x000000,
.icon = "󰍔",
.extensions = .{"md"},
.comment = "<!--",
.highlights = "tree-sitter-markdown/tree-sitter-markdown/queries/highlights.scm",
.injections = "tree-sitter-markdown/tree-sitter-markdown/queries/injections.scm",
.language_server = .{ "marksman", "server" },
.formatter = .{ "prettier", "--parser", "markdown" },
};
pub const @"markdown-inline" = .{
.description = "Markdown (inline)",
.color = 0x000000,
.icon = "󰍔",
.extensions = .{},
.comment = "<!--",
.highlights = "tree-sitter-markdown/tree-sitter-markdown-inline/queries/highlights.scm",
.injections = "tree-sitter-markdown/tree-sitter-markdown-inline/queries/injections.scm",
};
pub const nasm = .{
.description = "Assembly Language (nasm)",
.extensions = .{ "asm", "nasm" },
.comment = "#",
.injections = "tree-sitter-nasm/queries/injections.scm",
};
pub const nim = .{
.description = "Nim",
.color = 0xffe953,
.icon = "",
.extensions = .{"nim"},
.comment = "#",
.language_server = .{"nimlangserver"},
};
pub const nimble = .{
.description = "Nimble (nim)",
.color = 0xffe953,
.icon = "",
.extensions = .{"nimble"},
.highlights = toml.highlights,
.comment = "#",
.parser = toml.parser,
};
pub const ninja = .{
.description = "Ninja",
.extensions = .{"ninja"},
.comment = "#",
};
pub const nix = .{
.description = "Nix",
.color = 0x5277C3,
.icon = "󱄅",
.extensions = .{"nix"},
.comment = "#",
.injections = "tree-sitter-nix/queries/injections.scm",
.language_server = .{"nixd"},
.formatter = .{"alejandra"},
};
pub const nu = .{
.description = "Nushell",
.color = 0x3AA675,
.icon = ">",
.extensions = .{ "nu", "nushell" },
.comment = "#",
.language_server = .{ "nu", "--lsp" },
.highlights = "tree-sitter-nu/queries/nu/highlights.scm",
.injections = "tree-sitter-nu/queries/nu/injections.scm",
};
pub const ocaml = .{
.description = "OCaml",
.color = 0xF18803,
.icon = "",
.extensions = .{ "ml", "mli" },
.comment = "(*",
.formatter = .{ "ocamlformat", "--profile=ocamlformat", "-" },
.language_server = .{ "ocamllsp", "--fallback-read-dot-merlin" },
};
pub const odin = .{
.description = "Odin",
.extensions = .{"odin"},
.comment = "//",
.parser = @import("file_type.zig").Parser("odin"),
.injections = "tree-sitter-odin/queries/injections.scm",
.language_server = .{"ols"},
.formatter = .{ "odinfmt", "-stdin" },
};
pub const openscad = .{
.description = "OpenSCAD",
.color = 0x000000,
.icon = "󰻫",
.extensions = .{"scad"},
.comment = "//",
.injections = "tree-sitter-openscad/queries/injections.scm",
.language_server = .{"openscad-lsp"},
};
pub const org = .{
.description = "Org Mode",
.icon = "",
.extensions = .{"org"},
.comment = "#",
};
pub const php = .{
.description = "PHP",
.color = 0x6181b6,
.icon = "󰌟",
.extensions = .{"php"},
.comment = "//",
.injections = "tree-sitter-php/queries/injections.scm",
.language_server = .{ "intelephense", "--stdio" },
};
pub const powershell = .{
.description = "PowerShell",
.color = 0x0873c5,
.icon = "",
.extensions = .{"ps1"},
.comment = "#",
};
pub const proto = .{
.description = "protobuf (proto)",
.extensions = .{"proto"},
.comment = "//",
};
pub const purescript = .{
.description = "PureScript",
.color = 0x14161a,
.icon = "",
.extensions = .{"purs"},
.comment = "--",
.injections = "tree-sitter-purescript/queries/injections.scm",
};
pub const python = .{
.description = "Python",
.color = 0xffd845,
.icon = "󰌠",
.extensions = .{ "py", "pyi" },
.comment = "#",
.first_line_matches = FirstLineMatch{ .prefix = "#!", .content = "python" },
.language_server = .{"pylsp"},
};
pub const regex = .{
.description = "Regular expression",
.extensions = .{},
.comment = "#",
};
pub const rpmspec = .{
.description = "RPM spec",
.color = 0xff0000,
.icon = "󱄛",
.extensions = .{"spec"},
.comment = "#",
};
pub const ruby = .{
.description = "Ruby",
.color = 0xd91404,
.icon = "󰴭",
.extensions = .{"rb"},
.comment = "#",
.language_server = .{"ruby-lsp"},
};
pub const rust = .{
.description = "Rust",
.color = 0x000000,
.icon = "󱘗",
.extensions = .{"rs"},
.comment = "//",
.injections = "tree-sitter-rust/queries/injections.scm",
.language_server = .{"rust-analyzer"},
.formatter = .{"rustfmt"},
};
pub const scheme = .{
.description = "Scheme",
.extensions = .{ "scm", "ss", "el" },
.comment = ";",
};
pub const sql = .{
.description = "SQL",
.icon = "󰆼",
.extensions = .{"sql"},
.comment = "--",
};
pub const @"ssh-config" = .{
.description = "SSH config",
.extensions = .{".ssh/config"},
.comment = "#",
};
pub const swift = .{
.description = "Swift",
.color = 0xf05138,
.icon = "󰛥",
.extensions = .{ "swift", "swiftinterface" },
.comment = "//",
.language_server = .{"sourcekit-lsp"},
.formatter = .{"swift-format"},
};
pub const verilog = .{
.description = "SystemVerilog",
.extensions = .{ "sv", "svh" },
.comment = "//",
.highlights = "nvim-treesitter/queries/verilog/highlights.scm",
.injections = "nvim-treesitter/queries/verilog/injections.scm",
.language_server = .{"verible-verilog-ls"},
.formatter = .{ "verible-verilog-format", "-" },
};
pub const toml = .{
.description = "TOML",
.extensions = .{ "toml", "ini" },
.comment = "#",
.highlights = "tree-sitter-toml/queries/highlights.scm",
.parser = @import("file_type.zig").Parser("toml"),
};
pub const typescript = .{
.description = "TypeScript",
.color = 0x007acc,
.icon = "󰛦",
.extensions = .{ "ts", "tsx" },
.comment = "//",
.language_server = .{ "typescript-language-server", "--stdio" },
.formatter = .{ "prettier", "--parser", "typescript" },
};
pub const typst = .{
.description = "Typst",
.color = 0x23b6bc,
.icon = "t",
.extensions = .{ "typst", "typ" },
.comment = "//",
.language_server = .{"tinymist"},
.highlights = "tree-sitter-typst/queries/typst/highlights.scm",
.injections = "tree-sitter-typst/queries/typst/injections.scm",
};
pub const uxntal = .{
.description = "Uxntal",
.extensions = .{"tal"},
.comment = "(",
};
pub const vim = .{
.description = "Vimscript",
.color = 0x007f00,
.icon = "",
.extensions = .{"vim"},
.comment = "\"",
.highlights = "tree-sitter-vim/queries/vim/highlights.scm",
.injections = "tree-sitter-vim/queries/vim/injections.scm",
};
pub const xml = .{
.description = "XML",
.icon = "󰗀",
.extensions = .{"xml"},
.comment = "<!--",
.highlights = "tree-sitter-xml/queries/xml/highlights.scm",
.first_line_matches = FirstLineMatch{ .prefix = "<?xml " },
.formatter = .{ "xmllint", "--format", "-" },
};
pub const yaml = .{
.description = "YAML",
.color = 0x000000,
.icon = "",
.extensions = .{ "yaml", "yml" },
.comment = "#",
};
pub const zig = .{
.description = "Zig",
.color = 0xf7a41d,
.icon = "",
.extensions = .{ "zig", "zon" },
.comment = "//",
.formatter = .{ "zig", "fmt", "--stdin" },
.language_server = .{"zls"},
.injections = "tree-sitter-zig/queries/injections.scm",
};
pub const ziggy = .{
.description = "Ziggy",
.color = 0xf7a41d,
.icon = "",
.extensions = .{ "ziggy", "zgy" },
.comment = "//",
.highlights = "tree-sitter-ziggy/tree-sitter-ziggy/queries/highlights.scm",
};
pub const @"ziggy-schema" = .{
.description = "Ziggy (schema)",
.color = 0xf7a41d,
.icon = "",
.extensions = .{ "ziggy-schema", "zyg-schema" },
.comment = "//",
.highlights = "tree-sitter-ziggy/tree-sitter-ziggy-schema/queries/highlights.scm",
};

View file

@ -1,213 +0,0 @@
const std = @import("std");
const build_options = @import("build_options");
const treez = if (build_options.use_tree_sitter)
@import("treez")
else
@import("treez_dummy.zig");
const Self = @This();
pub const Edit = treez.InputEdit;
pub const FileType = @import("file_type.zig");
pub const QueryCache = @import("QueryCache.zig");
pub const Range = treez.Range;
pub const Point = treez.Point;
const Input = treez.Input;
const Language = treez.Language;
const Parser = treez.Parser;
const Query = treez.Query;
pub const Node = treez.Node;
allocator: std.mem.Allocator,
lang: *const Language,
parser: *Parser,
query: *Query,
errors_query: *Query,
injections: ?*Query,
tree: ?*treez.Tree = null,
pub fn create(file_type: FileType, allocator: std.mem.Allocator, query_cache: *QueryCache) !*Self {
const query = try query_cache.get(file_type, .highlights);
errdefer query_cache.release(query, .highlights);
const errors_query = try query_cache.get(file_type, .errors);
errdefer query_cache.release(errors_query, .highlights);
const injections = try query_cache.get(file_type, .injections);
errdefer if (injections) |injections_| query_cache.release(injections_, .injections);
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = .{
.allocator = allocator,
.lang = file_type.lang_fn() orelse std.debug.panic("tree-sitter parser function failed for language: {s}", .{file_type.name}),
.parser = try Parser.create(),
.query = query,
.errors_query = errors_query,
.injections = injections,
};
try self.parser.setLanguage(self.lang);
return self;
}
pub fn create_file_type_static(allocator: std.mem.Allocator, lang_name: []const u8, query_cache: *QueryCache) !*Self {
const file_type = FileType.get_by_name_static(lang_name) orelse return error.NotFound;
return create(file_type, allocator, query_cache);
}
pub fn create_guess_file_type_static(allocator: std.mem.Allocator, content: []const u8, file_path: ?[]const u8, query_cache: *QueryCache) !*Self {
const file_type = FileType.guess_static(file_path, content) orelse return error.NotFound;
return create(file_type, allocator, query_cache);
}
pub fn destroy(self: *Self, query_cache: *QueryCache) void {
if (self.tree) |tree| tree.destroy();
query_cache.release(self.query, .highlights);
query_cache.release(self.errors_query, .highlights);
if (self.injections) |injections| query_cache.release(injections, .injections);
self.parser.destroy();
self.allocator.destroy(self);
}
pub fn reset(self: *Self) void {
if (self.tree) |tree| {
tree.destroy();
self.tree = null;
}
}
pub fn refresh_full(self: *Self, content: []const u8) !void {
self.reset();
self.tree = try self.parser.parseString(null, content);
}
pub fn edit(self: *Self, ed: Edit) void {
if (self.tree) |tree| tree.edit(&ed);
}
pub fn refresh_from_buffer(self: *Self, buffer: anytype, metrics: anytype) !void {
const old_tree = self.tree;
defer if (old_tree) |tree| tree.destroy();
const State = struct {
buffer: @TypeOf(buffer),
metrics: @TypeOf(metrics),
syntax: *Self,
result_buf: [1024]u8 = undefined,
};
var state: State = .{
.buffer = buffer,
.metrics = metrics,
.syntax = self,
};
const input: Input = .{
.payload = &state,
.read = struct {
fn read(payload: ?*anyopaque, _: u32, position: treez.Point, bytes_read: *u32) callconv(.c) [*:0]const u8 {
const ctx: *State = @ptrCast(@alignCast(payload orelse return ""));
const result = ctx.buffer.get_from_pos(.{ .row = position.row, .col = position.column }, &ctx.result_buf, ctx.metrics);
bytes_read.* = @intCast(result.len);
return @ptrCast(result.ptr);
}
}.read,
.encoding = .utf_8,
};
self.tree = try self.parser.parse(old_tree, input);
}
pub fn refresh_from_string(self: *Self, content: [:0]const u8) !void {
const old_tree = self.tree;
defer if (old_tree) |tree| tree.destroy();
const State = struct {
content: @TypeOf(content),
};
var state: State = .{
.content = content,
};
const input: Input = .{
.payload = &state,
.read = struct {
fn read(payload: ?*anyopaque, _: u32, position: treez.Point, bytes_read: *u32) callconv(.c) [*:0]const u8 {
bytes_read.* = 0;
const ctx: *State = @ptrCast(@alignCast(payload orelse return ""));
const pos = (find_line_begin(ctx.content, position.row) orelse return "") + position.column;
if (pos >= ctx.content.len) return "";
bytes_read.* = @intCast(ctx.content.len - pos);
return ctx.content[pos..].ptr;
}
}.read,
.encoding = .utf_8,
};
self.tree = try self.parser.parse(old_tree, input);
}
fn find_line_begin(s: []const u8, line: usize) ?usize {
var idx: usize = 0;
var at_line: usize = 0;
while (idx < s.len) {
if (at_line == line)
return idx;
if (s[idx] == '\n')
at_line += 1;
idx += 1;
}
return null;
}
fn CallBack(comptime T: type) type {
return fn (ctx: T, sel: Range, scope: []const u8, id: u32, capture_idx: usize, node: *const Node) error{Stop}!void;
}
pub fn render(self: *const Self, ctx: anytype, comptime cb: CallBack(@TypeOf(ctx)), range: ?Range) !void {
const cursor = try Query.Cursor.create();
defer cursor.destroy();
const tree = self.tree orelse return;
cursor.execute(self.query, tree.getRootNode());
if (range) |r| cursor.setPointRange(r.start_point, r.end_point);
while (cursor.nextMatch()) |match| {
var idx: usize = 0;
for (match.captures()) |capture| {
try cb(ctx, capture.node.getRange(), self.query.getCaptureNameForId(capture.id), capture.id, idx, &capture.node);
idx += 1;
}
}
}
pub fn highlights_at_point(self: *const Self, ctx: anytype, comptime cb: CallBack(@TypeOf(ctx)), point: Point) void {
const cursor = Query.Cursor.create() catch return;
defer cursor.destroy();
const tree = self.tree orelse return;
cursor.execute(self.query, tree.getRootNode());
cursor.setPointRange(.{ .row = point.row, .column = 0 }, .{ .row = point.row + 1, .column = 0 });
while (cursor.nextMatch()) |match| {
for (match.captures()) |capture| {
const range = capture.node.getRange();
const start = range.start_point;
const end = range.end_point;
const scope = self.query.getCaptureNameForId(capture.id);
if (start.row == point.row and start.column <= point.column and point.column < end.column)
cb(ctx, range, scope, capture.id, 0, &capture.node) catch return;
break;
}
}
return;
}
pub fn node_at_point_range(self: *const Self, range: Range) error{Stop}!treez.Node {
const tree = self.tree orelse return error.Stop;
const root_node = tree.getRootNode();
return treez.Node.externs.ts_node_descendant_for_point_range(root_node, range.start_point, range.end_point);
}
pub fn count_error_nodes(self: *const Self) usize {
const cursor = Query.Cursor.create() catch return std.math.maxInt(usize);
defer cursor.destroy();
const tree = self.tree orelse return 0;
cursor.execute(self.errors_query, tree.getRootNode());
var error_count: usize = 0;
while (cursor.nextMatch()) |match| for (match.captures()) |_| {
error_count += 1;
};
return error_count;
}

View file

@ -1,133 +0,0 @@
pub const InputEdit = extern struct {
start_byte: u32,
old_end_byte: u32,
new_end_byte: u32,
start_point: Point,
old_end_point: Point,
new_end_point: Point,
};
pub const Range = extern struct {
start_point: Point = .{},
end_point: Point = .{},
start_byte: u32 = 0,
end_byte: u32 = 0,
};
pub const Point = extern struct {
row: u32 = 0,
column: u32 = 0,
};
pub const InputEncoding = enum(c_uint) {
utf_8,
utf_16,
};
pub const Input = extern struct {
payload: ?*anyopaque,
read: ?*const fn (payload: ?*anyopaque, byte_index: u32, position: Point, bytes_read: *u32) callconv(.c) [*:0]const u8,
encoding: InputEncoding,
};
pub const Language = struct {
var dummy: @This() = .{};
pub fn LangFn() callconv(.c) ?*const Language {
return &dummy;
}
};
pub const Parser = struct {
var dummy: @This() = .{};
pub fn create() !*@This() {
return &dummy;
}
pub fn parse(_: *Parser, _: ?*Tree, _: Input) !*Tree {
return &Tree.dummy;
}
pub fn parseString(_: *@This(), _: ?[]const u8, _: []const u8) !?*Tree {
return null;
}
pub fn destroy(_: *@This()) void {}
pub fn setLanguage(_: *Parser, _: *const Language) !void {}
};
pub const Query = struct {
var dummy: @This() = .{};
pub fn create(_: *const Language, _: []const u8) !*Query {
return &dummy;
}
pub const Cursor = struct {
var dummy_: @This() = .{};
pub fn create() !*@This() {
return &dummy_;
}
pub fn execute(_: *@This(), _: *Query, _: *Node) void {}
pub fn setPointRange(_: *@This(), _: Point, _: Point) void {}
pub fn nextMatch(_: *@This()) ?*Match {
return null;
}
pub fn destroy(_: *@This()) void {}
pub const Match = struct {
pub fn captures(_: *@This()) []Capture {
return &[_]Capture{};
}
};
pub const Capture = struct {
id: u32,
node: Node,
};
};
pub fn getCaptureNameForId(_: *@This(), _: u32) []const u8 {
return "";
}
pub fn destroy(_: *@This()) void {}
};
pub const Tree = struct {
var dummy: @This() = .{};
pub fn getRootNode(_: *@This()) *Node {
return &Node.dummy;
}
pub fn destroy(_: *@This()) void {}
pub fn edit(_: *Tree, _: *const InputEdit) void {}
};
pub const Node = struct {
var dummy: @This() = .{};
pub fn getRange(_: *const @This()) Range {
return .{};
}
pub fn asSExpressionString(_: *const @This()) []const u8 {
return "";
}
pub fn freeSExpressionString(_: []const u8) void {}
pub fn getParent(_: *const @This()) Node {
return dummy;
}
pub fn getChild(_: *const @This(), _: usize) Node {
return dummy;
}
pub fn getChildCount(_: *const @This()) usize {
return 0;
}
pub fn getNamedChild(_: *const @This(), _: usize) Node {
return dummy;
}
pub fn getNamedChildCount(_: *const @This()) usize {
return 0;
}
pub fn isNull(_: *const @This()) bool {
return true;
}
pub const externs = struct {
pub fn ts_node_next_sibling(_: Node) Node {
return Node.dummy;
}
pub fn ts_node_prev_sibling(_: Node) Node {
return Node.dummy;
}
pub fn ts_node_next_named_sibling(_: Node) Node {
return Node.dummy;
}
pub fn ts_node_prev_named_sibling(_: Node) Node {
return Node.dummy;
}
pub fn ts_node_descendant_for_point_range(_: *const Node, _: Point, _: Point) Node {
return Node.dummy;
}
};
};

View file

@ -1,140 +0,0 @@
const std = @import("std");
const cbor = @import("cbor");
const treez = @import("treez");
pub const tss = @import("ts_serializer.zig");
const verbose = false;
pub fn main() anyerror!void {
const allocator = std.heap.c_allocator;
const args = try std.process.argsAlloc(allocator);
var opt_output_file_path: ?[]const u8 = null;
var i: usize = 1;
while (i < args.len) : (i += 1) {
const arg = args[i];
if (opt_output_file_path != null) fatal("duplicated {s} argument", .{arg});
opt_output_file_path = args[i];
}
const output_file_path = opt_output_file_path orelse fatal("missing output file", .{});
var output_file = std.fs.cwd().createFile(output_file_path, .{}) catch |err| {
fatal("unable to open '{s}': {s}", .{ output_file_path, @errorName(err) });
};
defer output_file.close();
var output = std.ArrayList(u8).init(allocator);
defer output.deinit();
const writer = output.writer();
try cbor.writeMapHeader(writer, file_types.len);
for (file_types) |file_type| {
const lang = file_type.lang_fn() orelse std.debug.panic("tree-sitter parser function failed for language: {s}", .{file_type.name});
try cbor.writeValue(writer, file_type.name);
try cbor.writeMapHeader(writer, if (file_type.injections) |_| 3 else 2);
const highlights_in = try treez.Query.create(lang, file_type.highlights);
const ts_highlights_in: *tss.TSQuery = @alignCast(@ptrCast(highlights_in));
const highlights_cb = try tss.toCbor(ts_highlights_in, allocator);
defer allocator.free(highlights_cb);
try cbor.writeValue(writer, "highlights");
try cbor.writeValue(writer, highlights_cb);
if (verbose)
std.log.info("file_type {s} highlights {d} bytes", .{ file_type.name, highlights_cb.len });
const errors_in = try treez.Query.create(lang, "(ERROR) @error");
const ts_errors_in: *tss.TSQuery = @alignCast(@ptrCast(errors_in));
const errors_cb = try tss.toCbor(ts_errors_in, allocator);
defer allocator.free(errors_cb);
try cbor.writeValue(writer, "errors");
try cbor.writeValue(writer, errors_cb);
if (verbose)
std.log.info("file_type {s} errors {d} bytes", .{ file_type.name, errors_cb.len });
if (file_type.injections) |injections| {
const injections_in = try treez.Query.create(lang, injections);
const ts_injections_in: *tss.TSQuery = @alignCast(@ptrCast(injections_in));
const injections_cb = try tss.toCbor(ts_injections_in, allocator);
defer allocator.free(injections_cb);
try cbor.writeValue(writer, "injections");
try cbor.writeValue(writer, injections_cb);
if (verbose)
std.log.info("file_type {s} injections {d} bytes", .{ file_type.name, injections_cb.len });
}
}
try output_file.writeAll(output.items);
if (verbose)
std.log.info("file_types total {d} bytes", .{output.items.len});
}
fn fatal(comptime format: []const u8, args: anytype) noreturn {
std.debug.print(format, args);
std.process.exit(1);
}
pub const file_types = load_file_types(@import("file_types.zig"));
const FileType = struct {
name: []const u8,
lang_fn: LangFn,
highlights: [:0]const u8,
injections: ?[:0]const u8,
};
const LangFn = *const fn () callconv(.c) ?*const treez.Language;
fn load_file_types(comptime Namespace: type) []const FileType {
comptime switch (@typeInfo(Namespace)) {
.@"struct" => |info| {
var count = 0;
for (info.decls) |_| count += 1;
var construct_types: [count]FileType = undefined;
var i = 0;
for (info.decls) |decl| {
const lang = decl.name;
const args = @field(Namespace, lang);
construct_types[i] = .{
.name = lang,
.lang_fn = if (@hasField(@TypeOf(args), "parser")) args.parser else get_parser(lang),
.highlights = if (@hasField(@TypeOf(args), "highlights"))
@embedFile(args.highlights)
else if (@hasField(@TypeOf(args), "highlights_list"))
@embedFile(args.highlights_list[0]) ++ "\n" ++ @embedFile(args.highlights_list[1])
else
@embedFile("tree-sitter-" ++ lang ++ "/queries/highlights.scm"),
.injections = if (@hasField(@TypeOf(args), "injections"))
@embedFile(args.injections)
else
null,
};
i += 1;
}
const types = construct_types;
return &types;
},
else => @compileError("expected tuple or struct type"),
};
}
fn get_parser(comptime lang: []const u8) LangFn {
const language_name = ft_func_name(lang);
return @extern(?LangFn, .{ .name = "tree_sitter_" ++ language_name }) orelse @compileError(std.fmt.comptimePrint("Cannot find extern tree_sitter_{s}", .{language_name}));
}
fn ft_func_name(comptime lang: []const u8) []const u8 {
var transform: [lang.len]u8 = undefined;
for (lang, 0..) |c, i|
transform[i] = if (c == '-') '_' else c;
const func_name = transform;
return &func_name;
}

View file

@ -1,297 +0,0 @@
/// This file *MUST* be kept in sync with tree-sitter/lib/src/query.c
/// It exactly represents the C structures in memory and must produce
/// the exact same results as the C tree-sitter library version used.
///
/// Yes,... it is not a public API! Here be dragons!
///
const std = @import("std");
const cbor = @import("cbor");
const build_options = @import("build_options");
const treez = if (build_options.use_tree_sitter) @import("treez") else @import("treez_dummy.zig");
pub const Slice = extern struct {
offset: u32,
length: u32,
pub fn cborEncode(self: *const @This(), writer: anytype) !void {
return cbor.writeArray(writer, self.*);
}
pub fn cborExtract(self: *@This(), iter: *[]const u8) cbor.Error!bool {
return cbor.matchValue(iter, .{
cbor.extract(&self.offset),
cbor.extract(&self.length),
});
}
};
pub fn Array(T: type) type {
return extern struct {
contents: ?*T,
size: u32,
capacity: u32,
pub fn cborEncode(self: *const @This(), writer: anytype) !void {
if (self.contents) |contents| {
const arr: []T = @as([*]T, @ptrCast(contents))[0..self.size];
try cbor.writeValue(writer, arr);
return;
}
try cbor.writeValue(writer, null);
}
pub fn cborExtract(self: *@This(), iter: *[]const u8, allocator: std.mem.Allocator) cbor.Error!bool {
var iter_ = iter.*;
if (cbor.matchValue(&iter_, cbor.null_) catch false) {
iter.* = iter_;
self.contents = null;
self.size = 0;
self.capacity = 0;
return true;
}
if (T == u8) {
var arr: []const u8 = undefined;
if (try cbor.matchValue(iter, cbor.extract(&arr))) {
self.contents = @constCast(@ptrCast(arr.ptr));
self.size = @intCast(arr.len);
self.capacity = @intCast(arr.len);
return true;
}
return false;
}
var i: usize = 0;
var n = try cbor.decodeArrayHeader(iter);
var arr: []T = try allocator.alloc(T, n);
while (n > 0) : (n -= 1) {
if (comptime cbor.isExtractableAlloc(T)) {
if (!(cbor.matchValue(iter, cbor.extractAlloc(&arr[i], allocator)) catch return false))
return false;
} else {
if (!(cbor.matchValue(iter, cbor.extract(&arr[i])) catch return false))
return false;
}
i += 1;
}
self.contents = @constCast(@ptrCast(arr.ptr));
self.size = @intCast(arr.len);
self.capacity = @intCast(arr.len);
return true;
}
};
}
pub const SymbolTable = extern struct {
characters: Array(u8),
slices: Array(Slice),
pub fn cborEncode(self: *const @This(), writer: anytype) !void {
return cbor.writeArray(writer, self.*);
}
pub fn cborExtract(self: *@This(), iter: *[]const u8, allocator: std.mem.Allocator) cbor.Error!bool {
return cbor.matchValue(iter, .{
cbor.extractAlloc(&self.characters, allocator),
cbor.extractAlloc(&self.slices, allocator),
});
}
};
pub const CaptureQuantifiers = Array(u8);
pub const PatternEntry = extern struct {
step_index: u16,
pattern_index: u16,
is_rooted: bool,
pub fn cborEncode(self: *const @This(), writer: anytype) !void {
return cbor.writeArray(writer, self.*);
}
pub fn cborExtract(self: *@This(), iter: *[]const u8) cbor.Error!bool {
return cbor.matchValue(iter, .{
cbor.extract(&self.step_index),
cbor.extract(&self.pattern_index),
cbor.extract(&self.is_rooted),
});
}
};
pub const QueryPattern = extern struct {
steps: Slice,
predicate_steps: Slice,
start_byte: u32,
end_byte: u32,
is_non_local: bool,
pub fn cborEncode(self: *const @This(), writer: anytype) !void {
return cbor.writeArray(writer, self.*);
}
pub fn cborExtract(self: *@This(), iter: *[]const u8, allocator: std.mem.Allocator) cbor.Error!bool {
return cbor.matchValue(iter, .{
cbor.extractAlloc(&self.steps, allocator),
cbor.extractAlloc(&self.predicate_steps, allocator),
cbor.extract(&self.start_byte),
cbor.extract(&self.end_byte),
cbor.extract(&self.is_non_local),
});
}
};
pub const StepOffset = extern struct {
byte_offset: u32,
step_index: u16,
pub fn cborEncode(self: *const @This(), writer: anytype) !void {
return cbor.writeArray(writer, self.*);
}
pub fn cborExtract(self: *@This(), iter: *[]const u8) cbor.Error!bool {
return cbor.matchValue(iter, .{
cbor.extract(&self.byte_offset),
cbor.extract(&self.step_index),
});
}
};
pub const MAX_STEP_CAPTURE_COUNT = 3;
pub const TSSymbol = u16;
pub const TSFieldId = u16;
pub const QueryStep = extern struct {
symbol: TSSymbol,
supertype_symbol: TSSymbol,
field: TSFieldId,
capture_ids: [MAX_STEP_CAPTURE_COUNT]u16,
depth: u16,
alternative_index: u16,
negated_field_list_id: u16,
// is_named: u1,
// is_immediate: u1,
// is_last_child: u1,
// is_pass_through: u1,
// is_dead_end: u1,
// alternative_is_immediate: u1,
// contains_captures: u1,
// root_pattern_guaranteed: u1,
flags8: u8,
// parent_pattern_guaranteed: u1,
flags16: u8,
pub fn cborEncode(self: *const @This(), writer: anytype) !void {
return cbor.writeArray(writer, self.*);
}
pub fn cborExtract(self: *@This(), iter: *[]const u8) cbor.Error!bool {
return cbor.matchValue(iter, .{
cbor.extract(&self.symbol),
cbor.extract(&self.supertype_symbol),
cbor.extract(&self.field),
cbor.extract(&self.capture_ids),
cbor.extract(&self.depth),
cbor.extract(&self.alternative_index),
cbor.extract(&self.negated_field_list_id),
cbor.extract(&self.flags8),
cbor.extract(&self.flags16),
});
}
};
pub const PredicateStep = extern struct {
pub const Type = enum(c_uint) {
done,
capture,
string,
};
type: Type,
value_id: u32,
pub fn cborEncode(self: *const @This(), writer: anytype) !void {
return cbor.writeArray(writer, self.*);
}
pub fn cborExtract(self: *@This(), iter: *[]const u8) cbor.Error!bool {
return cbor.matchValue(iter, .{
cbor.extract(&self.type),
cbor.extract(&self.value_id),
});
}
};
pub const TSQuery = extern struct {
captures: SymbolTable,
predicate_values: SymbolTable,
capture_quantifiers: Array(CaptureQuantifiers),
steps: Array(QueryStep),
pattern_map: Array(PatternEntry),
predicate_steps: Array(PredicateStep),
patterns: Array(QueryPattern),
step_offsets: Array(StepOffset),
negated_fields: Array(TSFieldId),
string_buffer: Array(u8),
repeat_symbols_with_rootless_patterns: Array(TSSymbol),
language: usize,
// language: ?*const treez.Language,
wildcard_root_pattern_count: u16,
pub fn cborEncode(self: *const @This(), writer: anytype) !void {
return cbor.writeArray(writer, self.*);
}
pub fn cborExtract(self: *@This(), iter: *[]const u8, allocator: std.mem.Allocator) cbor.Error!bool {
const result = cbor.matchValue(iter, .{
cbor.extractAlloc(&self.captures, allocator),
cbor.extractAlloc(&self.predicate_values, allocator),
cbor.extractAlloc(&self.capture_quantifiers, allocator),
cbor.extractAlloc(&self.steps, allocator),
cbor.extractAlloc(&self.pattern_map, allocator),
cbor.extractAlloc(&self.predicate_steps, allocator),
cbor.extractAlloc(&self.patterns, allocator),
cbor.extractAlloc(&self.step_offsets, allocator),
cbor.extractAlloc(&self.negated_fields, allocator),
cbor.extractAlloc(&self.string_buffer, allocator),
cbor.extractAlloc(&self.repeat_symbols_with_rootless_patterns, allocator),
cbor.extract(&self.language),
cbor.extract(&self.wildcard_root_pattern_count),
});
self.language = 0;
return result;
}
};
pub const SerializeError = error{OutOfMemory};
pub fn toCbor(query: *TSQuery, allocator: std.mem.Allocator) SerializeError![]const u8 {
var cb: std.ArrayListUnmanaged(u8) = .empty;
defer cb.deinit(allocator);
try cbor.writeValue(cb.writer(allocator), query.*);
return cb.toOwnedSlice(allocator);
}
pub const DeserializeError = error{
OutOfMemory,
IntegerTooLarge,
IntegerTooSmall,
InvalidType,
TooShort,
InvalidFloatType,
InvalidArrayType,
InvalidPIntType,
JsonIncompatibleType,
InvalidQueryCbor,
NotAnObject,
BadArrayAllocExtract,
};
pub fn fromCbor(cb: []const u8, allocator: std.mem.Allocator) DeserializeError!struct { *TSQuery, *std.heap.ArenaAllocator } {
var arena = try allocator.create(std.heap.ArenaAllocator);
errdefer allocator.destroy(arena);
arena.* = std.heap.ArenaAllocator.init(allocator);
errdefer arena.deinit();
const query = try arena.allocator().create(TSQuery);
query.* = undefined;
var iter: []const u8 = cb;
if (!try cbor.matchValue(&iter, cbor.extractAlloc(query, arena.allocator())))
return error.InvalidQueryCbor;
return .{ query, arena };
}

View file

@ -12,6 +12,8 @@ pub fn Options(context: type) type {
label: []const u8 = "Enter text",
pos: Widget.Box = .{ .y = 0, .x = 0, .w = 12, .h = 1 },
ctx: Context,
padding: u8 = 1,
icon: ?[]const u8 = null,
on_click: *const fn (ctx: context, button: *State(Context)) void = do_nothing,
on_render: *const fn (ctx: context, button: *State(Context), theme: *const Widget.Theme) bool = on_render_default,
@ -29,18 +31,22 @@ pub fn Options(context: type) type {
self.plane.set_style(style_label);
self.plane.fill(" ");
self.plane.home();
for (0..self.opts.padding) |_| _ = self.plane.putchar(" ");
if (self.icon_width > 0) if (self.opts.icon) |icon| {
_ = self.plane.print("{s}", .{icon}) catch {};
};
if (self.text.items.len > 0) {
_ = self.plane.print(" {s} ", .{self.text.items}) catch {};
_ = self.plane.print("{s} ", .{self.text.items}) catch {};
} else {
_ = self.plane.print(" {s} ", .{self.label.items}) catch {};
_ = self.plane.print("{s} ", .{self.label.items}) catch {};
}
if (self.cursor) |cursor| {
const pos: c_int = @intCast(cursor);
if (tui.config().enable_terminal_cursor) {
const y, const x = self.plane.rel_yx_to_abs(0, pos + 1);
const y, const x = self.plane.rel_yx_to_abs(0, pos + self.opts.padding + self.icon_width);
tui.rdr().cursor_enable(y, x, tui.get_cursor_shape()) catch {};
} else {
self.plane.cursor_move_yx(0, pos + 1) catch return false;
self.plane.cursor_move_yx(0, pos + self.opts.padding + self.icon_width) catch return false;
var cell = self.plane.cell_init();
_ = self.plane.at_cursor_cell(&cell) catch return false;
cell.set_style(theme.editor_cursor);
@ -68,6 +74,7 @@ pub fn create(ctx_type: type, allocator: std.mem.Allocator, parent: Plane, opts:
.opts = opts,
.label = std.ArrayList(u8).init(allocator),
.text = std.ArrayList(u8).init(allocator),
.icon_width = @intCast(if (tui.config().show_fileicons) if (opts.icon) |icon| n.egc_chunk_width(icon, 0, 1) else 0 else 0),
};
try self.label.appendSlice(self.opts.label);
self.opts.label = self.label.items;
@ -83,6 +90,7 @@ pub fn State(ctx_type: type) type {
label: std.ArrayList(u8),
opts: Options(ctx_type),
text: std.ArrayList(u8),
icon_width: c_int,
cursor: ?usize = 0,
const Self = @This();

View file

@ -14,13 +14,15 @@ pub const scroll_lines = 3;
pub fn Options(context: type) type {
return struct {
ctx: Context,
style: Widget.Type,
on_click: *const fn (ctx: context, button: *Button.State(*State(Context))) void = do_nothing,
on_click4: *const fn (menu: **State(Context), button: *Button.State(*State(Context))) void = do_nothing_click,
on_click5: *const fn (menu: **State(Context), button: *Button.State(*State(Context))) void = do_nothing_click,
on_render: *const fn (ctx: context, button: *Button.State(*State(Context)), theme: *const Widget.Theme, selected: bool) bool = on_render_default,
on_layout: *const fn (ctx: context, button: *Button.State(*State(Context))) Widget.Layout = on_layout_default,
on_resize: *const fn (ctx: context, menu: *State(Context), box: Widget.Box) void = on_resize_default,
prepare_resize: *const fn (ctx: context, menu: *State(Context), box: Widget.Box) Widget.Box = prepare_resize_default,
after_resize: *const fn (ctx: context, menu: *State(Context), box: Widget.Box) void = after_resize_default,
on_scroll: ?EventHandler = null,
pub const Context = context;
@ -46,23 +48,26 @@ pub fn Options(context: type) type {
return .{ .static = 1 };
}
pub fn on_resize_default(_: context, state: *State(Context), box_: Widget.Box) void {
pub fn prepare_resize_default(_: context, state: *State(Context), box_: Widget.Box) Widget.Box {
var box = box_;
box.h = if (box_.h == 0) state.menu.widgets.items.len else box_.h;
state.container.resize(box);
return box;
}
pub fn after_resize_default(_: context, _: *State(Context), _: Widget.Box) void {}
};
}
pub fn create(ctx_type: type, allocator: std.mem.Allocator, parent: Plane, opts: Options(ctx_type)) !*State(ctx_type) {
const self = try allocator.create(State(ctx_type));
errdefer allocator.destroy(self);
const container = try WidgetList.createH(allocator, parent, @typeName(@This()), .dynamic);
const container = try WidgetList.createHStyled(allocator, parent, @typeName(@This()), .dynamic, opts.style);
self.* = .{
.allocator = allocator,
.menu = try WidgetList.createV(allocator, container.plane, @typeName(@This()), .dynamic),
.container = container,
.container_widget = container.widget(),
.frame_widget = null,
.scrollbar = if (tui.config().show_scrollbars)
if (opts.on_scroll) |on_scroll| (try scrollbar_v.create(allocator, parent, null, on_scroll)).dynamic_cast(scrollbar_v).? else null
else
@ -72,7 +77,8 @@ pub fn create(ctx_type: type, allocator: std.mem.Allocator, parent: Plane, opts:
self.menu.ctx = self;
self.menu.on_render = State(ctx_type).on_render_menu;
container.ctx = self;
container.on_resize = State(ctx_type).on_resize_container;
container.prepare_resize = State(ctx_type).prepare_resize;
container.after_resize = State(ctx_type).after_resize;
try container.add(self.menu.widget());
if (self.scrollbar) |sb| try container.add(sb.widget());
return self;
@ -84,6 +90,7 @@ pub fn State(ctx_type: type) type {
menu: *WidgetList,
container: *WidgetList,
container_widget: Widget,
frame_widget: ?Widget,
scrollbar: ?*scrollbar_v,
opts: options_type,
selected: ?usize = null,
@ -146,9 +153,14 @@ pub fn State(ctx_type: type) type {
self.render_idx = 0;
}
fn on_resize_container(ctx: ?*anyopaque, _: *WidgetList, box: Widget.Box) void {
fn prepare_resize(ctx: ?*anyopaque, _: *WidgetList, box: Widget.Box) Widget.Box {
const self: *Self = @ptrCast(@alignCast(ctx));
self.opts.on_resize(self.*.opts.ctx, self, box);
return self.opts.prepare_resize(self.*.opts.ctx, self, box);
}
fn after_resize(ctx: ?*anyopaque, _: *WidgetList, box: Widget.Box) void {
const self: *Self = @ptrCast(@alignCast(ctx));
self.opts.after_resize(self.*.opts.ctx, self, box);
}
pub fn on_layout(self: **Self, button: *Button.State(*Self)) Widget.Layout {
@ -170,7 +182,7 @@ pub fn State(ctx_type: type) type {
}
pub fn walk(self: *Self, walk_ctx: *anyopaque, f: Widget.WalkFn) bool {
return self.menu.walk(walk_ctx, f, &self.container_widget);
return self.menu.walk(walk_ctx, f, if (self.frame_widget) |*frame| frame else &self.container_widget);
}
pub fn count(self: *Self) usize {

View file

@ -14,6 +14,7 @@ pub const Error = (cbor.Error || cbor.JsonEncodeError || error{
ThespianSpawnFailed,
NoProject,
ProjectManagerFailed,
InvalidProjectDirectory,
SendFailed,
});

View file

@ -10,6 +10,9 @@ pub const Box = @import("Box.zig");
pub const Theme = @import("theme");
pub const themes = @import("themes").themes;
pub const scopes = @import("themes").scopes;
pub const Type = @import("config").WidgetType;
pub const StyleTag = @import("config").WidgetStyle;
pub const Style = @import("WidgetStyle.zig");
ptr: *anyopaque,
plane: *Plane,

View file

@ -6,6 +6,7 @@ const tp = @import("thespian");
const Plane = @import("renderer").Plane;
const tui = @import("tui.zig");
const Widget = @import("Widget.zig");
const Box = @import("Box.zig");
@ -26,25 +27,35 @@ widgets: ArrayList(WidgetState),
layout_: Layout,
layout_empty: bool = true,
direction: Direction,
box: ?Widget.Box = null,
deco_box: Widget.Box,
ctx: ?*anyopaque = null,
on_render: *const fn (ctx: ?*anyopaque, theme: *const Widget.Theme) void = on_render_default,
after_render: *const fn (ctx: ?*anyopaque, theme: *const Widget.Theme) void = on_render_default,
on_resize: *const fn (ctx: ?*anyopaque, self: *Self, pos_: Widget.Box) void = on_resize_default,
prepare_resize: *const fn (ctx: ?*anyopaque, self: *Self, box: Widget.Box) Widget.Box = prepare_resize_default,
after_resize: *const fn (ctx: ?*anyopaque, self: *Self, box: Widget.Box) void = after_resize_default,
on_layout: *const fn (ctx: ?*anyopaque, self: *Self) Widget.Layout = on_layout_default,
widget_type: Widget.Type,
pub fn createH(allocator: Allocator, parent: Plane, name: [:0]const u8, layout_: Layout) error{OutOfMemory}!*Self {
return createHStyled(allocator, parent, name, layout_, .none);
}
pub fn createHStyled(allocator: Allocator, parent: Plane, name: [:0]const u8, layout_: Layout, widget_type: Widget.Type) error{OutOfMemory}!*Self {
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = try init(allocator, parent, name, .horizontal, layout_, Box{});
self.* = try init(allocator, parent, name, .horizontal, layout_, Box{}, widget_type);
self.plane.hide();
return self;
}
pub fn createV(allocator: Allocator, parent: Plane, name: [:0]const u8, layout_: Layout) !*Self {
return createVStyled(allocator, parent, name, layout_, .none);
}
pub fn createVStyled(allocator: Allocator, parent: Plane, name: [:0]const u8, layout_: Layout, widget_type: Widget.Type) !*Self {
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = try init(allocator, parent, name, .vertical, layout_, Box{});
self.* = try init(allocator, parent, name, .vertical, layout_, Box{}, widget_type);
self.plane.hide();
return self;
}
@ -57,15 +68,21 @@ pub fn createBox(allocator: Allocator, parent: Plane, name: [:0]const u8, dir: D
return self;
}
fn init(allocator: Allocator, parent: Plane, name: [:0]const u8, dir: Direction, layout_: Layout, box: Box) !Self {
return .{
.plane = try Plane.init(&box.opts(name), parent),
fn init(allocator: Allocator, parent: Plane, name: [:0]const u8, dir: Direction, layout_: Layout, box_: Box, widget_type: Widget.Type) !Self {
var self: Self = .{
.plane = undefined,
.parent = parent,
.allocator = allocator,
.widgets = ArrayList(WidgetState).init(allocator),
.layout_ = layout_,
.direction = dir,
.widget_type = widget_type,
.deco_box = undefined,
};
const padding = tui.get_widget_style(self.widget_type).padding;
self.deco_box = self.from_client_box(box_, padding);
self.plane = try Plane.init(&self.deco_box.opts(name), parent);
return self;
}
pub fn widget(self: *Self) Widget {
@ -147,18 +164,27 @@ pub fn update(self: *Self) void {
}
pub fn render(self: *Self, theme: *const Widget.Theme) bool {
const widget_style = tui.get_widget_style(self.widget_type);
const padding = widget_style.padding;
for (self.widgets.items) |*w| if (!w.layout.eql(w.widget.layout())) {
self.refresh_layout();
self.refresh_layout(padding);
break;
};
self.on_render(self.ctx, theme);
self.render_decoration(theme, widget_style);
const client_box = self.to_client_box(self.deco_box, padding);
var more = false;
for (self.widgets.items) |*w|
for (self.widgets.items) |*w| {
const widget_box = w.widget.box();
if (client_box.y + client_box.h <= widget_box.y) break;
if (client_box.x + client_box.w <= widget_box.x) break;
if (w.widget.render(theme)) {
more = true;
};
}
}
self.after_render(self.ctx, theme);
return more;
@ -166,6 +192,41 @@ pub fn render(self: *Self, theme: *const Widget.Theme) bool {
fn on_render_default(_: ?*anyopaque, _: *const Widget.Theme) void {}
fn render_decoration(self: *Self, theme: *const Widget.Theme, widget_style: *const Widget.Style) void {
const style = Widget.Style.theme_style_from_type(self.widget_type, theme);
const padding = widget_style.padding;
const border = widget_style.border;
const plane = &self.plane;
const box = self.deco_box;
plane.set_style(style);
plane.fill(" ");
if (padding.top > 0 and padding.left > 0) put_at_pos(plane, 0, 0, border.nw);
if (padding.top > 0 and padding.right > 0) put_at_pos(plane, 0, box.w - 1, border.ne);
if (padding.bottom > 0 and padding.left > 0 and box.h > 0) put_at_pos(plane, box.h - 1, 0, border.sw);
if (padding.bottom > 0 and padding.right > 0 and box.h > 0) put_at_pos(plane, box.h - 1, box.w - 1, border.se);
{
const start: usize = if (padding.left > 0) 1 else 0;
const end: usize = if (padding.right > 0 and box.w > 0) box.w - 1 else box.w;
if (padding.top > 0) for (start..end) |x| put_at_pos(plane, 0, x, border.n);
if (padding.bottom > 0) for (start..end) |x| put_at_pos(plane, box.h - 1, x, border.s);
}
{
const start: usize = if (padding.top > 0) 1 else 0;
const end: usize = if (padding.bottom > 0 and box.h > 0) box.h - 1 else box.h;
if (padding.left > 0) for (start..end) |y| put_at_pos(plane, y, 0, border.w);
if (padding.right > 0) for (start..end) |y| put_at_pos(plane, y, box.w - 1, border.e);
}
}
inline fn put_at_pos(plane: *Plane, y: usize, x: usize, egc: []const u8) void {
plane.cursor_move_yx(@intCast(y), @intCast(x)) catch return;
plane.putchar(egc);
}
pub fn receive(self: *Self, from_: tp.pid_ref, m: tp.message) error{Exit}!bool {
if (try m.match(.{ "H", tp.more }))
return false;
@ -176,6 +237,13 @@ pub fn receive(self: *Self, from_: tp.pid_ref, m: tp.message) error{Exit}!bool {
return false;
}
fn get_size_a_const(self: *Self, pos: *const Widget.Box) usize {
return switch (self.direction) {
.vertical => pos.h,
.horizontal => pos.w,
};
}
fn get_size_a(self: *Self, pos: *Widget.Box) *usize {
return switch (self.direction) {
.vertical => &pos.h,
@ -183,6 +251,13 @@ fn get_size_a(self: *Self, pos: *Widget.Box) *usize {
};
}
fn get_size_b_const(self: *Self, pos: *const Widget.Box) usize {
return switch (self.direction) {
.vertical => pos.w,
.horizontal => pos.h,
};
}
fn get_size_b(self: *Self, pos: *Widget.Box) *usize {
return switch (self.direction) {
.vertical => &pos.w,
@ -190,6 +265,13 @@ fn get_size_b(self: *Self, pos: *Widget.Box) *usize {
};
}
fn get_loc_a_const(self: *Self, pos: *const Widget.Box) usize {
return switch (self.direction) {
.vertical => pos.y,
.horizontal => pos.x,
};
}
fn get_loc_a(self: *Self, pos: *Widget.Box) *usize {
return switch (self.direction) {
.vertical => &pos.y,
@ -197,6 +279,13 @@ fn get_loc_a(self: *Self, pos: *Widget.Box) *usize {
};
}
fn get_loc_b_const(self: *Self, pos: *const Widget.Box) usize {
return switch (self.direction) {
.vertical => pos.x,
.horizontal => pos.y,
};
}
fn get_loc_b(self: *Self, pos: *Widget.Box) *usize {
return switch (self.direction) {
.vertical => &pos.x,
@ -204,28 +293,62 @@ fn get_loc_b(self: *Self, pos: *Widget.Box) *usize {
};
}
fn refresh_layout(self: *Self) void {
return if (self.box) |box| self.handle_resize(box);
fn refresh_layout(self: *Self, padding: Widget.Style.Margin) void {
return self.handle_resize(self.to_client_box(self.deco_box, padding));
}
pub fn handle_resize(self: *Self, pos: Widget.Box) void {
self.on_resize(self.ctx, self, pos);
pub fn handle_resize(self: *Self, box: Widget.Box) void {
const padding = tui.get_widget_style(self.widget_type).padding;
const client_box_ = self.prepare_resize(self.ctx, self, self.to_client_box(box, padding));
self.deco_box = self.from_client_box(client_box_, padding);
self.do_resize(padding);
self.after_resize(self.ctx, self, self.to_client_box(self.deco_box, padding));
}
fn on_resize_default(_: ?*anyopaque, self: *Self, pos: Widget.Box) void {
self.resize(pos);
pub inline fn to_client_box(_: *const Self, box_: Widget.Box, padding: Widget.Style.Margin) Widget.Box {
const total_y_padding = padding.top + padding.bottom;
const total_x_padding = padding.left + padding.right;
var box = box_;
box.y += padding.top;
box.h -= if (box.h > total_y_padding) total_y_padding else box.h;
box.x += padding.left;
box.w -= if (box.w > total_x_padding) total_x_padding else box.w;
return box;
}
inline fn from_client_box(_: *const Self, box_: Widget.Box, padding: Widget.Style.Margin) Widget.Box {
const total_y_padding = padding.top + padding.bottom;
const total_x_padding = padding.left + padding.right;
const y = if (box_.y < padding.top) padding.top else box_.y;
const x = if (box_.x < padding.left) padding.left else box_.x;
var box = box_;
box.y = y - padding.top;
box.h += total_y_padding;
box.x = x - padding.left;
box.w += total_x_padding;
return box;
}
fn prepare_resize_default(_: ?*anyopaque, _: *Self, box: Widget.Box) Widget.Box {
return box;
}
fn after_resize_default(_: ?*anyopaque, _: *Self, _: Widget.Box) void {}
fn on_layout_default(_: ?*anyopaque, self: *Self) Widget.Layout {
return self.layout_;
}
pub fn resize(self: *Self, pos_: Widget.Box) void {
self.box = pos_;
var pos = pos_;
self.plane.move_yx(@intCast(pos.y), @intCast(pos.x)) catch return;
self.plane.resize_simple(@intCast(pos.h), @intCast(pos.w)) catch return;
const total = self.get_size_a(&pos).*;
pub fn resize(self: *Self, box: Widget.Box) void {
return self.handle_resize(box);
}
fn do_resize(self: *Self, padding: Widget.Style.Margin) void {
const client_box = self.to_client_box(self.deco_box, padding);
const deco_box = self.deco_box;
self.plane.move_yx(@intCast(deco_box.y), @intCast(deco_box.x)) catch return;
self.plane.resize_simple(@intCast(deco_box.h), @intCast(deco_box.w)) catch return;
const total = self.get_size_a_const(&client_box);
var avail = total;
var statics: usize = 0;
var dynamics: usize = 0;
@ -245,7 +368,7 @@ pub fn resize(self: *Self, pos_: Widget.Box) void {
const dyn_size = avail / if (dynamics > 0) dynamics else 1;
const rounded: usize = if (dyn_size * dynamics < avail) avail - dyn_size * dynamics else 0;
var cur_loc: usize = self.get_loc_a(&pos).*;
var cur_loc: usize = self.get_loc_a_const(&client_box);
var first = true;
for (self.widgets.items) |*w| {
@ -261,8 +384,8 @@ pub fn resize(self: *Self, pos_: Widget.Box) void {
self.get_loc_a(&w_pos).* = cur_loc;
cur_loc += size;
self.get_size_b(&w_pos).* = self.get_size_b(&pos).*;
self.get_loc_b(&w_pos).* = self.get_loc_b(&pos).*;
self.get_size_b(&w_pos).* = self.get_size_b_const(&client_box);
self.get_loc_b(&w_pos).* = self.get_loc_b_const(&client_box);
w.widget.resize(w_pos);
}
}

141
src/tui/WidgetStyle.zig Normal file
View file

@ -0,0 +1,141 @@
padding: Margin = Margin.@"0",
border: Border = Border.blank,
pub const WidgetType = @import("config").WidgetType;
pub const WidgetStyle = @import("config").WidgetStyle;
pub const Padding = struct {
pub const Unit = u16;
};
pub const Margin = struct {
const Unit = Padding.Unit;
top: Unit,
bottom: Unit,
left: Unit,
right: Unit,
const @"0": Margin = .{ .top = 0, .bottom = 0, .left = 0, .right = 0 };
const @"1": Margin = .{ .top = 1, .bottom = 1, .left = 1, .right = 1 };
const @"2": Margin = .{ .top = 2, .bottom = 2, .left = 2, .right = 2 };
const @"3": Margin = .{ .top = 3, .bottom = 3, .left = 3, .right = 3 };
const @"1/2": Margin = .{ .top = 1, .bottom = 1, .left = 2, .right = 2 };
const @"2/1": Margin = .{ .top = 2, .bottom = 2, .left = 1, .right = 1 };
const @"2/3": Margin = .{ .top = 2, .bottom = 2, .left = 3, .right = 3 };
const @"2/4": Margin = .{ .top = 2, .bottom = 2, .left = 4, .right = 4 };
const @"top/bottom/1": Margin = .{ .top = 1, .bottom = 1, .left = 0, .right = 0 };
const @"top/bottom/2": Margin = .{ .top = 2, .bottom = 2, .left = 0, .right = 0 };
const @"left/right/1": Margin = .{ .top = 0, .bottom = 0, .left = 1, .right = 1 };
const @"left/right/2": Margin = .{ .top = 0, .bottom = 0, .left = 2, .right = 2 };
};
pub const Border = struct {
nw: []const u8,
n: []const u8,
ne: []const u8,
e: []const u8,
se: []const u8,
s: []const u8,
sw: []const u8,
w: []const u8,
const blank: Border = .{ .nw = " ", .n = " ", .ne = " ", .e = " ", .se = " ", .s = " ", .sw = " ", .w = " " };
const box: Border = .{ .nw = "", .n = "", .ne = "", .e = "", .se = "", .s = "", .sw = "", .w = "" };
const @"rounded box": Border = .{ .nw = "", .n = "", .ne = "", .e = "", .se = "", .s = "", .sw = "", .w = "" };
const @"double box": Border = .{ .nw = "", .n = "", .ne = "", .e = "", .se = "", .s = "", .sw = "", .w = "" };
const @"single/double box (top/bottom)": Border = .{ .nw = "", .n = "", .ne = "", .e = "", .se = "", .s = "", .sw = "", .w = "" };
const @"single/double box (left/right)": Border = .{ .nw = "", .n = "", .ne = "", .e = "", .se = "", .s = "", .sw = "", .w = "" };
const @"dotted box (braille)": Border = .{ .nw = "", .n = "", .ne = "", .e = "", .se = "", .s = "", .sw = "", .w = "" };
const @"thick box (half)": Border = .{ .nw = "", .n = "", .ne = "", .e = "", .se = "", .s = "", .sw = "", .w = "" };
const @"thick box (sextant)": Border = .{ .nw = "🬕", .n = "🬂", .ne = "🬨", .e = "", .se = "🬷", .s = "🬭", .sw = "🬲", .w = "" };
const @"thick box (octant)": Border = .{ .nw = "𜵊", .n = "🮂", .ne = "𜶘", .e = "", .se = "𜷕", .s = "", .sw = "𜷀", .w = "" };
const @"extra thick box": Border = .{ .nw = "", .n = "", .ne = "", .e = "", .se = "", .s = "", .sw = "", .w = "" };
const @"round thick box": Border = .{ .nw = "", .n = "", .ne = "", .e = "", .se = "", .s = "", .sw = "", .w = "" };
};
const compact: @This() = .{};
const spacious: @This() = .{
.padding = Margin.@"1",
.border = Border.blank,
};
const boxed: @This() = .{
.padding = Margin.@"1",
.border = Border.box,
};
const rounded_boxed: @This() = .{
.padding = Margin.@"1",
.border = Border.@"rounded box",
};
const double_boxed: @This() = .{
.padding = Margin.@"1",
.border = Border.@"double box",
};
const single_double_top_bottom_boxed: @This() = .{
.padding = Margin.@"1",
.border = Border.@"single/double box (top/bottom)",
};
const single_double_left_right_boxed: @This() = .{
.padding = Margin.@"1",
.border = Border.@"single/double box (left/right)",
};
const dotted_boxed: @This() = .{
.padding = Margin.@"1",
.border = Border.@"dotted box (braille)",
};
const thick_boxed: @This() = .{
.padding = Margin.@"1/2",
.border = Border.@"thick box (octant)",
};
const extra_thick_boxed: @This() = .{
.padding = Margin.@"1/2",
.border = Border.@"extra thick box",
};
const bars_top_bottom: @This() = .{
.padding = Margin.@"top/bottom/1",
.border = Border.@"thick box (octant)",
};
const bars_left_right: @This() = .{
.padding = Margin.@"left/right/1",
.border = Border.@"thick box (octant)",
};
pub fn from_tag(tag: WidgetStyle) *const @This() {
return switch (tag) {
.compact => &compact,
.spacious => &spacious,
.boxed => &boxed,
.double_boxed => &double_boxed,
.rounded_boxed => &rounded_boxed,
.single_double_top_bottom_boxed => &single_double_top_bottom_boxed,
.single_double_left_right_boxed => &single_double_left_right_boxed,
.dotted_boxed => &dotted_boxed,
.thick_boxed => &thick_boxed,
.extra_thick_boxed => &extra_thick_boxed,
.bars_top_bottom => &bars_top_bottom,
.bars_left_right => &bars_left_right,
};
}
const Theme = @import("Widget.zig").Theme;
pub fn theme_style_from_type(style_type: WidgetType, theme: *const Theme) Theme.Style {
return switch (style_type) {
.none => theme.editor,
.palette => .{ .fg = theme.editor_widget_border.fg, .bg = theme.editor_widget.bg },
.panel => .{ .fg = theme.editor_widget_border.fg, .bg = theme.editor.bg },
.home => .{ .fg = theme.editor_widget_border.fg, .bg = theme.editor.bg },
};
}

View file

@ -55,6 +55,8 @@ pub const whitespace = struct {
};
};
pub const PosType = enum { column, byte };
pub const Match = struct {
begin: Cursor = Cursor{},
end: Cursor = Cursor{},
@ -351,6 +353,9 @@ pub const Editor = struct {
diag_hints: usize = 0,
completions: std.ArrayListUnmanaged(u8) = .empty,
completion_row: usize = 0,
completion_col: usize = 0,
completion_is_complete: bool = true,
enable_auto_save: bool,
enable_format_on_save: bool,
@ -432,6 +437,7 @@ pub const Editor = struct {
tp.extract_cbor(&cursels_cbor),
}))
return error.RestoreStateMatch;
self.refresh_tab_width();
if (op == .open_file)
try self.open(file_path);
self.clipboard = if (clipboard.len > 0) try self.allocator.dupe(u8, clipboard) else null;
@ -468,7 +474,7 @@ pub const Editor = struct {
fn init(self: *Self, allocator: Allocator, n: Plane, buffer_manager: *Buffer.Manager) void {
const logger = log.logger("editor");
const frame_rate = tp.env.get().num("frame-rate");
const tab_width = tui.config().tab_width;
const tab_width = tui.get_tab_width();
const indent_mode = tui.config().indent_mode;
const indent_size = if (indent_mode == .tabs) tab_width else tui.config().indent_size;
self.* = Self{
@ -702,6 +708,23 @@ pub const Editor = struct {
return;
}
fn refresh_tab_width(self: *Self) void {
self.metrics = self.plane.metrics(self.tab_width);
switch (self.indent_mode) {
.spaces, .auto => {},
.tabs => self.indent_size = self.tab_width,
}
}
pub fn set_editor_tab_width(self: *Self, ctx: Context) Result {
var tab_width: usize = 0;
if (!try ctx.args.match(.{tp.extract(&tab_width)}))
return error.InvalidSetTabWidthArgument;
self.tab_width = tab_width;
self.refresh_tab_width();
}
pub const set_editor_tab_width_meta: Meta = .{ .arguments = &.{.integer} };
fn close(self: *Self) !void {
var meta = std.ArrayListUnmanaged(u8).empty;
defer meta.deinit(self.allocator);
@ -4457,7 +4480,9 @@ pub const Editor = struct {
};
}
fn cursel_smart_insert_line(self: *Self, root: Buffer.Root, cursel: *CurSel, b_allocator: std.mem.Allocator) !Buffer.Root {
const WSCollapseMode = enum { leave_ws, collapse_ws };
fn cursel_smart_insert_line(self: *Self, root: Buffer.Root, cursel: *CurSel, b_allocator: std.mem.Allocator, mode: WSCollapseMode) !Buffer.Root {
const row = cursel.cursor.row;
const leading_ws = @min(find_first_non_ws(root, row, self.metrics), cursel.cursor.col);
var sfa = std.heap.stackFallback(512, self.allocator);
@ -4468,7 +4493,8 @@ pub const Editor = struct {
_ = try writer.write("\n");
try self.generate_leading_ws(&writer, leading_ws);
var root_ = try self.insert(root, cursel, stream.items, b_allocator);
root_ = self.collapse_trailing_ws_line(root_, row, b_allocator);
if (mode == .collapse_ws)
root_ = self.collapse_trailing_ws_line(root_, row, b_allocator);
const leading_ws_ = find_first_non_ws(root_, cursel.cursor.row, self.metrics);
if (leading_ws_ > leading_ws and leading_ws_ > cursel.cursor.col) {
const sel = try cursel.enable_selection(root_, self.metrics);
@ -4499,11 +4525,11 @@ pub const Editor = struct {
(std.mem.eql(u8, egc_right, "(") and std.mem.eql(u8, egc_left, ")"));
};
root = try self.cursel_smart_insert_line(root, cursel, b.allocator);
root = try self.cursel_smart_insert_line(root, cursel, b.allocator, .collapse_ws);
if (smart_brace_indent) {
const cursor = cursel.cursor;
root = try self.cursel_smart_insert_line(root, cursel, b.allocator);
root = try self.cursel_smart_insert_line(root, cursel, b.allocator, .leave_ws);
cursel.cursor = cursor;
if (indent_extra)
root = try self.indent_cursel(root, cursel, b.allocator);
@ -5389,11 +5415,18 @@ pub const Editor = struct {
var column: usize = 0;
var have_sel: bool = false;
var sel: Selection = .{};
var pos_type: PosType = .column;
if (try ctx.args.match(.{
tp.extract(&line),
tp.extract(&column),
})) {
// self.logger.print("goto: l:{d} c:{d}", .{ line, column });
} else if (try ctx.args.match(.{
tp.extract(&line),
tp.extract(&column),
tp.extract(&pos_type),
})) {
// self.logger.print("goto: l:{d} c:{d}", .{ line, column });
} else if (try ctx.args.match(.{
tp.extract(&line),
tp.extract(&column),
@ -5404,9 +5437,29 @@ pub const Editor = struct {
})) {
// self.logger.print("goto: l:{d} c:{d} {any}", .{ line, column, sel });
have_sel = true;
} else if (try ctx.args.match(.{
tp.extract(&line),
tp.extract(&column),
tp.extract(&sel.begin.row),
tp.extract(&sel.begin.col),
tp.extract(&sel.end.row),
tp.extract(&sel.end.col),
tp.extract(&pos_type),
})) {
// self.logger.print("goto: l:{d} c:{d} {any} {}", .{ line, column, sel, pos_type });
have_sel = true;
} else return error.InvalidGotoLineAndColumnArgument;
self.cancel_all_selections();
const root = self.buf_root() catch return;
if (pos_type == .byte) {
column = root.pos_to_width(line - 1, column - 1, self.metrics) catch return;
column += 1;
if (have_sel) {
sel.begin.col = root.pos_to_width(sel.begin.row, sel.begin.col, self.metrics) catch return;
sel.end.col = root.pos_to_width(sel.end.row, sel.end.col, self.metrics) catch return;
}
// self.logger.print("goto_byte_pos: l:{d} c:{d} {any} {}", .{ line, column, sel, pos_type });
}
const primary = self.get_primary();
try primary.cursor.move_to(
root,
@ -5660,10 +5713,13 @@ pub const Editor = struct {
}
pub fn add_completion(self: *Self, row: usize, col: usize, is_incomplete: bool, msg: tp.message) Result {
if (!(row == self.completion_row and col == self.completion_col)) {
self.completions.clearRetainingCapacity();
self.completion_row = row;
self.completion_col = col;
}
try self.completions.appendSlice(self.allocator, msg.buf);
_ = row;
_ = col;
_ = is_incomplete;
self.completion_is_complete = is_incomplete;
}
pub fn select(self: *Self, ctx: Context) Result {
@ -6029,6 +6085,8 @@ pub const EditorWidget = struct {
last_btn: input.Mouse = .none,
last_btn_time_ms: i64 = 0,
last_btn_count: usize = 0,
last_btn_x: c_int = 0,
last_btn_y: c_int = 0,
hover: bool = false,
hover_timer: ?tp.Cancellable = null,
@ -6193,9 +6251,16 @@ pub const EditorWidget = struct {
fn mouse_click_button1(self: *Self, y: c_int, x: c_int, _: c_int, xoffset: c_int) Result {
const y_, const x_ = self.mouse_pos_abs(y, x, xoffset);
defer {
self.last_btn_y = y_;
self.last_btn_x = x_;
}
if (self.last_btn == input.mouse.BUTTON1) {
const click_time_ms = time.milliTimestamp() - self.last_btn_time_ms;
if (click_time_ms <= double_click_time_ms) {
if (click_time_ms <= double_click_time_ms and
self.last_btn_y == y_ and
self.last_btn_x == x_)
{
if (self.last_btn_count == 2) {
self.last_btn_count = 3;
try self.editor.primary_triple_click(y_, x_);

View file

@ -33,8 +33,10 @@ view_rows: usize = 0,
view_cols: usize = 0,
entries: std.ArrayList(Entry) = undefined,
selected: ?usize = null,
box: Widget.Box = .{},
const path_column_ratio = 4;
const widget_type: Widget.Type = .panel;
const Entry = struct {
path: []const u8,
@ -44,6 +46,7 @@ const Entry = struct {
end_pos: usize,
lines: []const u8,
severity: editor.Diagnostic.Severity = .Information,
pos_type: editor.PosType,
};
pub fn create(allocator: Allocator, parent: Plane) !Widget {
@ -56,6 +59,7 @@ pub fn create(allocator: Allocator, parent: Plane) !Widget {
.entries = std.ArrayList(Entry).init(allocator),
.menu = try Menu.create(*Self, allocator, tui.plane(), .{
.ctx = self,
.style = widget_type,
.on_render = handle_render_menu,
.on_scroll = EventHandler.bind(self, Self.handle_scroll),
.on_click4 = mouse_click_button4,
@ -84,11 +88,14 @@ fn scrollbar_style(sb: *scrollbar_v, theme: *const Widget.Theme) Widget.Theme.St
}
pub fn handle_resize(self: *Self, pos: Widget.Box) void {
const padding = tui.get_widget_style(widget_type).padding;
self.plane.move_yx(@intCast(pos.y), @intCast(pos.x)) catch return;
self.plane.resize_simple(@intCast(pos.h), @intCast(pos.w)) catch return;
self.menu.container_widget.resize(pos);
self.view_rows = pos.h;
self.view_cols = pos.w;
self.box = pos;
self.menu.container.resize(self.box);
const client_box = self.menu.container.to_client_box(pos, padding);
self.view_rows = client_box.h;
self.view_cols = client_box.w;
self.update_scrollbar();
}
@ -107,7 +114,7 @@ pub fn add_item(self: *Self, entry_: Entry) !void {
const writer = label.writer();
cbor.writeValue(writer, idx) catch return;
self.menu.add_item_with_handler(label.items, handle_menu_action) catch return;
self.menu.container_widget.resize(Widget.Box.from(self.plane));
self.menu.resize(self.box);
self.update_scrollbar();
}
@ -160,8 +167,8 @@ fn handle_render_menu(self: *Self, button: *Button.State(*Menu.State(*Self)), th
button.plane.home();
}
const entry = &self.entries.items[idx];
const pointer = if (selected) "" else " ";
_ = button.plane.print("{s} ", .{pointer}) catch {};
button.plane.set_style(style_label);
tui.render_pointer(&button.plane, selected);
var buf: [std.fs.max_path_bytes]u8 = undefined;
var removed_prefix: usize = 0;
const max_len = self.view_cols / path_column_ratio;
@ -244,6 +251,7 @@ fn handle_menu_action(menu: **Menu.State(*Self), button: *Button.State(*Menu.Sta
if (entry.begin_pos == 0) 0 else entry.begin_pos + 1,
entry.end_line,
entry.end_pos + 1,
entry.pos_type,
},
} }) catch |e| self.logger.err("navigate", e);
}

View file

@ -30,7 +30,7 @@ const style = struct {
\\open_recent_project
\\find_in_files
\\open_command_palette
\\select_task
\\run_task
\\add_task
\\open_config
\\open_gui_config
@ -48,7 +48,7 @@ const style = struct {
\\open_recent_project
\\find_in_files
\\open_command_palette
\\select_task
\\run_task
\\add_task
\\open_config
\\open_keybind_config
@ -70,6 +70,8 @@ fire: ?Fire = null,
commands: Commands = undefined,
menu: *Menu.State(*Self),
menu_w: usize = 0,
menu_label_max: usize = 0,
menu_count: usize = 0,
menu_len: usize = 0,
max_desc_len: usize = 0,
input_namespace: []const u8,
@ -79,6 +81,8 @@ home_style_bufs: [][]const u8,
const Self = @This();
const widget_type: Widget.Type = .home;
pub fn create(allocator: std.mem.Allocator, parent: Widget) !Widget {
const logger = log.logger("home");
const self = try allocator.create(Self);
@ -95,7 +99,11 @@ pub fn create(allocator: std.mem.Allocator, parent: Widget) !Widget {
.allocator = allocator,
.parent = parent.plane.*,
.plane = n,
.menu = try Menu.create(*Self, allocator, w.plane.*, .{ .ctx = self, .on_render = menu_on_render }),
.menu = try Menu.create(*Self, allocator, w.plane.*, .{
.ctx = self,
.style = widget_type,
.on_render = menu_on_render,
}),
.input_namespace = keybind.get_namespace(),
.home_style = home_style,
.home_style_bufs = home_style_bufs,
@ -103,7 +111,6 @@ pub fn create(allocator: std.mem.Allocator, parent: Widget) !Widget {
try self.commands.init(self);
var it = std.mem.splitAny(u8, self.home_style.menu_commands, "\n ");
while (it.next()) |command_name| {
self.menu_len += 1;
const id = command.get_id(command_name) orelse {
logger.print("{s} is not defined", .{command_name});
continue;
@ -112,11 +119,14 @@ pub fn create(allocator: std.mem.Allocator, parent: Widget) !Widget {
logger.print("{s} has no description", .{command_name});
continue;
};
self.menu_count += 1;
var hints = std.mem.splitScalar(u8, keybind_mode.keybind_hints.get(command_name) orelse "", ',');
const hint = hints.first();
self.max_desc_len = @max(self.max_desc_len, description.len + hint.len + 5);
try self.add_menu_command(command_name, description, hint, self.menu);
}
const padding = tui.get_widget_style(widget_type).padding;
self.menu_len = self.menu_count + padding.top + padding.bottom;
self.position_menu(15, 9);
return w;
}
@ -145,7 +155,9 @@ fn add_menu_command(self: *Self, command_name: []const u8, description: []const
_ = try writer.write(leader);
try writer.print(" :{s}", .{hint});
const label = fis.getWritten();
self.menu_w = @max(self.menu_w, label.len + 1);
const padding = tui.get_widget_style(widget_type).padding;
self.menu_label_max = @max(self.menu_label_max, label.len);
self.menu_w = self.menu_label_max + 2 + padding.left + padding.right;
}
var value = std.ArrayList(u8).init(self.allocator);
@ -228,8 +240,8 @@ fn menu_on_render(self: *Self, button: *Button.State(*Menu.State(*Self)), theme:
} else {
button.plane.set_style_bg_transparent(style_text);
}
const pointer = if (selected) "" else " ";
_ = button.plane.print("{s}{s}", .{ pointer, description }) catch {};
tui.render_pointer(&button.plane, selected);
_ = button.plane.print("{s}", .{description}) catch {};
if (button.active or button.hover or selected) {
button.plane.set_style(style_leader);
} else {
@ -323,13 +335,13 @@ pub fn render(self: *Self, theme: *const Widget.Theme) bool {
_ = self.plane.print("{s}", .{debug_warning_text}) catch return false;
}
const more = self.menu.render(theme);
const more = self.menu.container.render(theme);
return more or self.fire != null;
}
fn position_menu(self: *Self, y: usize, x: usize) void {
const box = Widget.Box.from(self.plane);
self.menu.resize(.{ .y = box.y + y, .x = box.x + x, .w = self.menu_w });
self.menu.resize(.{ .y = box.y + y, .x = box.x + x, .w = self.menu_w, .h = self.menu_len });
}
fn center(self: *Self, non_centered: usize, w: usize) usize {
@ -388,6 +400,16 @@ const cmds = struct {
}
pub const home_menu_activate_meta: Meta = .{};
pub fn home_next_widget_style(self: *Self, _: Ctx) Result {
tui.set_next_style(widget_type);
const padding = tui.get_widget_style(widget_type).padding;
self.menu_len = self.menu_count + padding.top + padding.bottom;
self.menu_w = self.menu_label_max + 2 + padding.left + padding.right;
tui.need_render();
try tui.save_config();
}
pub const home_next_widget_style_meta: Meta = .{};
pub fn home_sheeran(self: *Self, _: Ctx) Result {
self.fire = if (self.fire) |*fire| ret: {
fire.deinit();

View file

@ -322,6 +322,7 @@ const cmds = struct {
if (!try ctx.args.match(.{tp.extract(&project_dir)}))
return;
try self.check_all_not_dirty();
try project_manager.open(project_dir);
for (self.editors.items) |editor| {
editor.clear_diagnostics();
try editor.close_file(.{});
@ -332,7 +333,6 @@ const cmds = struct {
try self.toggle_panel_view(filelist_view, false);
self.buffer_manager.deinit();
self.buffer_manager = Buffer.Manager.init(self.allocator);
try project_manager.open(project_dir);
const project = tp.env.get().str("project");
tui.rdr().set_terminal_working_directory(project);
if (self.top_bar) |bar| _ = try bar.msg(.{ "PRJ", "open" });
@ -827,8 +827,11 @@ const cmds = struct {
tp.more,
})) return error.InvalidAddDiagnosticArgument;
file_path = project_manager.normalize_file_path(file_path);
if (self.get_active_editor()) |editor| if (std.mem.eql(u8, file_path, editor.file_path orelse ""))
try editor.add_completion(row, col, is_incomplete, ctx.args);
if (self.get_active_editor()) |editor| {
if (std.mem.eql(u8, file_path, editor.file_path orelse ""))
try editor.add_completion(row, col, is_incomplete, ctx.args);
try tui.open_overlay(@import("mode/overlay/completion_palette.zig").Type);
}
}
pub const add_completion_meta: Meta = .{
.arguments = &.{
@ -938,6 +941,12 @@ const cmds = struct {
}
pub const open_previous_file_meta: Meta = .{ .description = "Open the previous file" };
pub fn open_most_recent_file(self: *Self, _: Ctx) Result {
if (try project_manager.request_most_recent_file(self.allocator)) |file_path|
self.show_file_async(file_path);
}
pub const open_most_recent_file_meta: Meta = .{ .description = "Open the last changed file" };
pub fn system_paste(self: *Self, _: Ctx) Result {
if (builtin.os.tag == .windows) {
const text = try @import("renderer").request_windows_clipboard(self.allocator);
@ -1315,7 +1324,7 @@ pub fn write_restore_info(self: *Self) void {
fn read_restore_info(self: *Self) !void {
const file_name = try root.get_restore_file_name();
const file = try std.fs.cwd().openFile(file_name, .{ .mode = .read_only });
const file = try std.fs.openFileAbsolute(file_name, .{ .mode = .read_only });
defer file.close();
const stat = try file.stat();
var buf = try self.allocator.alloc(u8, @intCast(stat.size));
@ -1414,6 +1423,7 @@ fn add_find_in_files_result(
.end_pos = @max(1, end_pos) - 1,
.lines = lines,
.severity = severity,
.pos_type = .byte,
}) catch |e| return tp.exit_error(e, @errorReturnTrace());
}

View file

@ -2,6 +2,7 @@ const std = @import("std");
const tp = @import("thespian");
const cbor = @import("cbor");
const log = @import("log");
const file_type_config = @import("file_type_config");
const root = @import("root");
const input = @import("input");
@ -20,6 +21,7 @@ pub fn Create(options: type) type {
return struct {
allocator: std.mem.Allocator,
file_path: std.ArrayList(u8),
rendered_mini_buffer: std.ArrayListUnmanaged(u8) = .empty,
query: std.ArrayList(u8),
match: std.ArrayList(u8),
entries: std.ArrayList(Entry),
@ -33,8 +35,12 @@ pub fn Create(options: type) type {
const Entry = struct {
name: []const u8,
type: enum { dir, file, link },
type: EntryType,
file_type: []const u8,
icon: []const u8,
color: u24,
};
const EntryType = enum { dir, file, link };
pub fn create(allocator: std.mem.Allocator, _: command.Context) !struct { tui.Mode, tui.MiniMode } {
const self = try allocator.create(Self);
@ -66,6 +72,7 @@ pub fn Create(options: type) type {
self.match.deinit();
self.query.deinit();
self.file_path.deinit();
self.rendered_mini_buffer.deinit(self.allocator);
self.allocator.destroy(self);
}
@ -80,7 +87,11 @@ pub fn Create(options: type) type {
}
fn clear_entries(self: *Self) void {
for (self.entries.items) |entry| self.allocator.free(entry.name);
for (self.entries.items) |entry| {
self.allocator.free(entry.name);
self.allocator.free(entry.file_type);
self.allocator.free(entry.icon);
}
self.entries.clearRetainingCapacity();
}
@ -112,14 +123,11 @@ pub fn Create(options: type) type {
self.complete_trigger_count = 0;
self.file_path.clearRetainingCapacity();
if (self.match.items.len > 0) {
try self.construct_path(self.query.items, .{ .name = self.match.items, .type = .file }, 0);
try self.construct_path(self.query.items, self.match.items, .file, 0);
} else {
try self.file_path.appendSlice(self.query.items);
}
if (tui.mini_mode()) |mini_mode| {
mini_mode.text = self.file_path.items;
mini_mode.cursor = tui.egc_chunk_width(self.file_path.items, 0, 1);
}
self.update_mini_mode_text();
return;
}
self.complete_trigger_count -= 1;
@ -139,12 +147,7 @@ pub fn Create(options: type) type {
}
fn process_project_manager(self: *Self, m: tp.message) MessageFilter.Error!void {
defer {
if (tui.mini_mode()) |mini_mode| {
mini_mode.text = self.file_path.items;
mini_mode.cursor = tui.egc_chunk_width(self.file_path.items, 0, 1);
}
}
defer self.update_mini_mode_text();
var count: usize = undefined;
if (try cbor.match(m.buf, .{ "PRJ", "path_entry", tp.more })) {
return self.process_path_entry(m);
@ -158,18 +161,31 @@ pub fn Create(options: type) type {
fn process_path_entry(self: *Self, m: tp.message) MessageFilter.Error!void {
var path: []const u8 = undefined;
var file_name: []const u8 = undefined;
if (try cbor.match(m.buf, .{ tp.any, tp.any, tp.any, tp.extract(&path), "DIR", tp.extract(&file_name) })) {
(try self.entries.addOne()).* = .{ .name = try self.allocator.dupe(u8, file_name), .type = .dir };
} else if (try cbor.match(m.buf, .{ tp.any, tp.any, tp.any, tp.extract(&path), "LINK", tp.extract(&file_name) })) {
(try self.entries.addOne()).* = .{ .name = try self.allocator.dupe(u8, file_name), .type = .link };
} else if (try cbor.match(m.buf, .{ tp.any, tp.any, tp.any, tp.extract(&path), "FILE", tp.extract(&file_name) })) {
(try self.entries.addOne()).* = .{ .name = try self.allocator.dupe(u8, file_name), .type = .file };
var file_type: []const u8 = undefined;
var icon: []const u8 = undefined;
var color: u24 = undefined;
if (try cbor.match(m.buf, .{ tp.any, tp.any, tp.any, tp.extract(&path), "DIR", tp.extract(&file_name), tp.extract(&file_type), tp.extract(&icon), tp.extract(&color) })) {
try self.add_entry(file_name, .dir, file_type, icon, color);
} else if (try cbor.match(m.buf, .{ tp.any, tp.any, tp.any, tp.extract(&path), "LINK", tp.extract(&file_name), tp.extract(&file_type), tp.extract(&icon), tp.extract(&color) })) {
try self.add_entry(file_name, .link, file_type, icon, color);
} else if (try cbor.match(m.buf, .{ tp.any, tp.any, tp.any, tp.extract(&path), "FILE", tp.extract(&file_name), tp.extract(&file_type), tp.extract(&icon), tp.extract(&color) })) {
try self.add_entry(file_name, .file, file_type, icon, color);
} else {
log.logger("file_browser").err("receive", tp.unexpected(m));
}
tui.need_render();
}
fn add_entry(self: *Self, file_name: []const u8, entry_type: EntryType, file_type: []const u8, icon: []const u8, color: u24) !void {
(try self.entries.addOne()).* = .{
.name = try self.allocator.dupe(u8, file_name),
.type = entry_type,
.file_type = try self.allocator.dupe(u8, file_type),
.icon = try self.allocator.dupe(u8, icon),
.color = color,
};
}
fn do_complete(self: *Self) !void {
self.complete_trigger_count = @min(self.complete_trigger_count, self.entries.items.len);
self.file_path.clearRetainingCapacity();
@ -179,9 +195,10 @@ pub fn Create(options: type) type {
if (self.total_matches == 1)
self.complete_trigger_count = 0;
} else if (self.entries.items.len > 0) {
try self.construct_path(self.query.items, self.entries.items[self.complete_trigger_count - 1], self.complete_trigger_count - 1);
const entry = self.entries.items[self.complete_trigger_count - 1];
try self.construct_path(self.query.items, entry.name, entry.type, self.complete_trigger_count - 1);
} else {
try self.construct_path(self.query.items, .{ .name = "", .type = .file }, 0);
try self.construct_path(self.query.items, "", .file, 0);
}
if (self.match.items.len > 0)
if (self.total_matches > 1)
@ -192,14 +209,14 @@ pub fn Create(options: type) type {
message("{d}/{d}", .{ self.matched_entry + 1, self.entries.items.len });
}
fn construct_path(self: *Self, path_: []const u8, entry: Entry, entry_no: usize) error{OutOfMemory}!void {
fn construct_path(self: *Self, path_: []const u8, entry_name: []const u8, entry_type: EntryType, entry_no: usize) error{OutOfMemory}!void {
self.matched_entry = entry_no;
const path = project_manager.normalize_file_path(path_);
try self.file_path.appendSlice(path);
if (path.len > 0 and path[path.len - 1] != std.fs.path.sep)
try self.file_path.append(std.fs.path.sep);
try self.file_path.appendSlice(entry.name);
if (entry.type == .dir)
try self.file_path.appendSlice(entry_name);
if (entry_type == .dir)
try self.file_path.append(std.fs.path.sep);
}
@ -212,7 +229,7 @@ pub fn Create(options: type) type {
if (try prefix_compare_icase(self.allocator, self.match.items, entry.name)) {
matched += 1;
if (matched == self.complete_trigger_count) {
try self.construct_path(self.query.items, entry, i);
try self.construct_path(self.query.items, entry.name, entry.type, i);
found_match = i;
}
last = entry;
@ -222,11 +239,11 @@ pub fn Create(options: type) type {
self.total_matches = matched;
if (found_match) |_| return;
if (last) |entry| {
try self.construct_path(self.query.items, entry, last_no);
try self.construct_path(self.query.items, entry.name, entry.type, last_no);
self.complete_trigger_count = matched;
} else {
message("no match for '{s}'", .{self.match.items});
try self.construct_path(self.query.items, .{ .name = self.match.items, .type = .file }, 0);
try self.construct_path(self.query.items, self.match.items, .file, 0);
}
}
@ -264,8 +281,15 @@ pub fn Create(options: type) type {
fn update_mini_mode_text(self: *Self) void {
if (tui.mini_mode()) |mini_mode| {
mini_mode.text = self.file_path.items;
mini_mode.cursor = tui.egc_chunk_width(self.file_path.items, 0, 1);
const icon = if (self.entries.items.len > 0 and self.complete_trigger_count > 0)
self.entries.items[self.complete_trigger_count - 1].icon
else
" ";
self.rendered_mini_buffer.clearRetainingCapacity();
const writer = self.rendered_mini_buffer.writer(self.allocator);
writer.print("{s} {s}", .{ icon, self.file_path.items }) catch {};
mini_mode.text = self.rendered_mini_buffer.items;
mini_mode.cursor = tui.egc_chunk_width(self.file_path.items, 0, 1) + 3;
}
}

View file

@ -1,140 +1,23 @@
const tp = @import("thespian");
const key = @import("renderer").input.key;
const mod = @import("renderer").input.modifier;
const event_type = @import("renderer").input.event_type;
const keybind = @import("keybind");
const command = @import("command");
const EventHandler = @import("EventHandler");
const tui = @import("../../tui.zig");
const Allocator = @import("std").mem.Allocator;
const fmt = @import("std").fmt;
pub const Type = @import("numeric_input.zig").Create(@This());
pub const create = Type.create;
const Self = @This();
const name = "goto";
const Commands = command.Collection(cmds);
allocator: Allocator,
buf: [30]u8 = undefined,
input: ?usize = null,
start: usize,
commands: Commands = undefined,
pub fn create(allocator: Allocator, _: command.Context) !struct { tui.Mode, tui.MiniMode } {
const editor = tui.get_active_editor() orelse return error.NotFound;
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = .{
.allocator = allocator,
.start = editor.get_primary().cursor.row + 1,
};
try self.commands.init(self);
var mode = try keybind.mode("mini/goto", allocator, .{
.insert_command = "mini_mode_insert_bytes",
});
mode.event_handler = EventHandler.to_owned(self);
return .{ mode, .{ .name = name } };
pub fn name(_: *Type) []const u8 {
return "goto";
}
pub fn deinit(self: *Self) void {
self.commands.deinit();
self.allocator.destroy(self);
pub fn start(_: *Type) usize {
const editor = tui.get_active_editor() orelse return 1;
return editor.get_primary().cursor.row + 1;
}
pub fn receive(self: *Self, _: tp.pid_ref, _: tp.message) error{Exit}!bool {
self.update_mini_mode_text();
return false;
}
pub const preview = goto;
pub const apply = goto;
pub const cancel = goto;
fn update_mini_mode_text(self: *Self) void {
if (tui.mini_mode()) |mini_mode| {
mini_mode.text = if (self.input) |linenum|
(fmt.bufPrint(&self.buf, "{d}", .{linenum}) catch "")
else
"";
mini_mode.cursor = tui.egc_chunk_width(mini_mode.text, 0, 1);
}
}
fn goto(self: *Self) void {
fn goto(self: *Type, _: command.Context) void {
command.executeName("goto_line", command.fmt(.{self.input orelse self.start})) catch {};
}
fn insert_char(self: *Self, char: u8) void {
switch (char) {
'0' => {
if (self.input) |linenum| self.input = linenum * 10;
},
'1'...'9' => {
const digit: usize = @intCast(char - '0');
self.input = if (self.input) |x| x * 10 + digit else digit;
},
else => {},
}
}
fn insert_bytes(self: *Self, bytes: []const u8) void {
for (bytes) |c| self.insert_char(c);
}
const cmds = struct {
pub const Target = Self;
const Ctx = command.Context;
const Meta = command.Metadata;
const Result = command.Result;
pub fn mini_mode_reset(self: *Self, _: Ctx) Result {
self.input = null;
self.update_mini_mode_text();
}
pub const mini_mode_reset_meta: Meta = .{ .description = "Clear input" };
pub fn mini_mode_cancel(self: *Self, _: Ctx) Result {
self.input = null;
self.update_mini_mode_text();
self.goto();
command.executeName("exit_mini_mode", .{}) catch {};
}
pub const mini_mode_cancel_meta: Meta = .{ .description = "Cancel input" };
pub fn mini_mode_delete_backwards(self: *Self, _: Ctx) Result {
if (self.input) |linenum| {
const newval = if (linenum < 10) 0 else linenum / 10;
self.input = if (newval == 0) null else newval;
self.update_mini_mode_text();
self.goto();
}
}
pub const mini_mode_delete_backwards_meta: Meta = .{ .description = "Delete backwards" };
pub fn mini_mode_insert_code_point(self: *Self, ctx: Ctx) Result {
var keypress: usize = 0;
if (!try ctx.args.match(.{tp.extract(&keypress)}))
return error.InvalidGotoInsertCodePointArgument;
switch (keypress) {
'0'...'9' => self.insert_char(@intCast(keypress)),
else => {},
}
self.update_mini_mode_text();
self.goto();
}
pub const mini_mode_insert_code_point_meta: Meta = .{ .arguments = &.{.integer} };
pub fn mini_mode_insert_bytes(self: *Self, ctx: Ctx) Result {
var bytes: []const u8 = undefined;
if (!try ctx.args.match(.{tp.extract(&bytes)}))
return error.InvalidGotoInsertBytesArgument;
self.insert_bytes(bytes);
self.update_mini_mode_text();
self.goto();
}
pub const mini_mode_insert_bytes_meta: Meta = .{ .arguments = &.{.string} };
pub fn mini_mode_paste(self: *Self, ctx: Ctx) Result {
return mini_mode_insert_bytes(self, ctx);
}
pub const mini_mode_paste_meta: Meta = .{ .arguments = &.{.string} };
};

View file

@ -0,0 +1,148 @@
const tp = @import("thespian");
const key = @import("renderer").input.key;
const mod = @import("renderer").input.modifier;
const event_type = @import("renderer").input.event_type;
const keybind = @import("keybind");
const command = @import("command");
const EventHandler = @import("EventHandler");
const tui = @import("../../tui.zig");
const Allocator = @import("std").mem.Allocator;
const fmt = @import("std").fmt;
pub fn Create(options: type) type {
return struct {
const Self = @This();
const Commands = command.Collection(cmds);
allocator: Allocator,
buf: [30]u8 = undefined,
input: ?usize = null,
start: usize,
ctx: command.Context,
commands: Commands = undefined,
pub fn create(allocator: Allocator, ctx: command.Context) !struct { tui.Mode, tui.MiniMode } {
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = .{
.allocator = allocator,
.ctx = .{ .args = try ctx.args.clone(allocator) },
.start = 0,
};
self.start = options.start(self);
try self.commands.init(self);
var mode = try keybind.mode("mini/numeric", allocator, .{
.insert_command = "mini_mode_insert_bytes",
});
mode.event_handler = EventHandler.to_owned(self);
return .{ mode, .{ .name = options.name(self) } };
}
pub fn deinit(self: *Self) void {
self.allocator.free(self.ctx.args.buf);
self.commands.deinit();
self.allocator.destroy(self);
}
pub fn receive(self: *Self, _: tp.pid_ref, _: tp.message) error{Exit}!bool {
self.update_mini_mode_text();
return false;
}
fn update_mini_mode_text(self: *Self) void {
if (tui.mini_mode()) |mini_mode| {
mini_mode.text = if (self.input) |linenum|
(fmt.bufPrint(&self.buf, "{d}", .{linenum}) catch "")
else
"";
mini_mode.cursor = tui.egc_chunk_width(mini_mode.text, 0, 1);
}
}
fn insert_char(self: *Self, char: u8) void {
switch (char) {
'0' => {
if (self.input) |linenum| self.input = linenum * 10;
},
'1'...'9' => {
const digit: usize = @intCast(char - '0');
self.input = if (self.input) |x| x * 10 + digit else digit;
},
else => {},
}
}
fn insert_bytes(self: *Self, bytes: []const u8) void {
for (bytes) |c| self.insert_char(c);
}
const cmds = struct {
pub const Target = Self;
const Ctx = command.Context;
const Meta = command.Metadata;
const Result = command.Result;
pub fn mini_mode_reset(self: *Self, _: Ctx) Result {
self.input = null;
self.update_mini_mode_text();
}
pub const mini_mode_reset_meta: Meta = .{ .description = "Clear input" };
pub fn mini_mode_cancel(self: *Self, _: Ctx) Result {
self.input = null;
self.update_mini_mode_text();
options.cancel(self, self.ctx);
command.executeName("exit_mini_mode", .{}) catch {};
}
pub const mini_mode_cancel_meta: Meta = .{ .description = "Cancel input" };
pub fn mini_mode_delete_backwards(self: *Self, _: Ctx) Result {
if (self.input) |linenum| {
const newval = if (linenum < 10) 0 else linenum / 10;
self.input = if (newval == 0) null else newval;
self.update_mini_mode_text();
options.preview(self, self.ctx);
}
}
pub const mini_mode_delete_backwards_meta: Meta = .{ .description = "Delete backwards" };
pub fn mini_mode_insert_code_point(self: *Self, ctx: Ctx) Result {
var keypress: usize = 0;
if (!try ctx.args.match(.{tp.extract(&keypress)}))
return error.InvalidGotoInsertCodePointArgument;
switch (keypress) {
'0'...'9' => self.insert_char(@intCast(keypress)),
else => {},
}
self.update_mini_mode_text();
options.preview(self, self.ctx);
}
pub const mini_mode_insert_code_point_meta: Meta = .{ .arguments = &.{.integer} };
pub fn mini_mode_insert_bytes(self: *Self, ctx: Ctx) Result {
var bytes: []const u8 = undefined;
if (!try ctx.args.match(.{tp.extract(&bytes)}))
return error.InvalidGotoInsertBytesArgument;
self.insert_bytes(bytes);
self.update_mini_mode_text();
options.preview(self, self.ctx);
}
pub const mini_mode_insert_bytes_meta: Meta = .{ .arguments = &.{.string} };
pub fn mini_mode_paste(self: *Self, ctx: Ctx) Result {
return mini_mode_insert_bytes(self, ctx);
}
pub const mini_mode_paste_meta: Meta = .{ .arguments = &.{.string} };
pub fn mini_mode_select(self: *Self, _: Ctx) Result {
options.apply(self, self.ctx);
command.executeName("exit_mini_mode", .{}) catch {};
}
pub const mini_mode_select_meta: Meta = .{ .description = "Select" };
};
};
}

View file

@ -0,0 +1,32 @@
const cbor = @import("cbor");
const command = @import("command");
const tui = @import("../../tui.zig");
pub const Type = @import("numeric_input.zig").Create(@This());
pub const create = Type.create;
pub fn name(_: *Type) []const u8 {
return " tab size";
}
pub fn start(self: *Type) usize {
const tab_width = if (tui.get_active_editor()) |editor| editor.tab_width else tui.get_tab_width();
self.input = tab_width;
return tab_width;
}
const default_cmd = "set_editor_tab_width";
pub const cancel = preview;
pub fn preview(self: *Type, _: command.Context) void {
command.executeName(default_cmd, command.fmt(.{self.input orelse self.start})) catch {};
}
pub fn apply(self: *Type, ctx: command.Context) void {
var cmd: []const u8 = undefined;
if (!(ctx.args.match(.{cbor.extract(&cmd)}) catch false))
cmd = default_cmd;
command.executeName(cmd, command.fmt(.{self.input orelse self.start})) catch {};
}

View file

@ -12,8 +12,7 @@ const Widget = @import("../../Widget.zig");
pub const label = "Switch buffers";
pub const name = " buffer";
pub const description = "buffer";
const dirty_indicator = "";
const hidden_indicator = "-";
pub const icon = "󰈞 ";
pub const Entry = struct {
label: []const u8,
@ -27,12 +26,7 @@ pub fn load_entries(palette: *Type) !usize {
const buffers = try buffer_manager.list_most_recently_used(palette.allocator);
defer palette.allocator.free(buffers);
for (buffers) |buffer| {
const indicator = if (buffer.is_dirty())
dirty_indicator
else if (buffer.is_hidden())
hidden_indicator
else
"";
const indicator = tui.get_buffer_state_indicator(buffer);
(try palette.entries.addOne()).* = .{
.label = buffer.get_file_path(),
.icon = buffer.file_type_icon orelse "",
@ -61,50 +55,7 @@ pub fn add_menu_entry(palette: *Type, entry: *Entry, matches: ?[]const usize) !v
}
pub fn on_render_menu(_: *Type, button: *Type.ButtonState, theme: *const Widget.Theme, selected: bool) bool {
const style_base = theme.editor_widget;
const style_label = if (button.active) theme.editor_cursor else if (button.hover or selected) theme.editor_selection else theme.editor_widget;
const style_hint = if (tui.find_scope_style(theme, "entity.name")) |sty| sty.style else style_label;
button.plane.set_base_style(style_base);
button.plane.erase();
button.plane.home();
button.plane.set_style(style_label);
if (button.active or button.hover or selected) {
button.plane.fill(" ");
button.plane.home();
}
button.plane.set_style(style_hint);
const pointer = if (selected) "" else " ";
_ = button.plane.print("{s}", .{pointer}) catch {};
var iter = button.opts.label;
var file_path_: []const u8 = undefined;
var icon: []const u8 = undefined;
var color: u24 = undefined;
if (!(cbor.matchString(&iter, &file_path_) catch false)) @panic("invalid buffer file path");
if (!(cbor.matchString(&iter, &icon) catch false)) @panic("invalid buffer file type icon");
if (!(cbor.matchInt(u24, &iter, &color) catch false)) @panic("invalid buffer file type color");
if (tui.config().show_fileicons) {
tui.render_file_icon(&button.plane, icon, color);
_ = button.plane.print(" ", .{}) catch {};
}
button.plane.set_style(style_label);
_ = button.plane.print(" {s} ", .{file_path_}) catch {};
var indicator: []const u8 = undefined;
if (!(cbor.matchString(&iter, &indicator) catch false))
indicator = "";
button.plane.set_style(style_hint);
_ = button.plane.print_aligned_right(0, "{s} ", .{indicator}) catch {};
var index: usize = 0;
var len = cbor.decodeArrayHeader(&iter) catch return false;
while (len > 0) : (len -= 1) {
if (cbor.matchValue(&iter, cbor.extract(&index)) catch break) {
tui.render_match_cell(&button.plane, 0, index + 4, theme) catch break;
} else break;
}
return false;
return tui.render_file_item_cbor(&button.plane, button.opts.label, button.active, selected, button.hover, theme);
}
fn select(menu: **Type.MenuState, button: *Type.ButtonState) void {

View file

@ -0,0 +1,175 @@
const std = @import("std");
const cbor = @import("cbor");
const tp = @import("thespian");
const root = @import("root");
const command = @import("command");
const tui = @import("../../tui.zig");
pub const Type = @import("palette.zig").Create(@This());
const module_name = @typeName(@This());
const Widget = @import("../../Widget.zig");
pub const label = "Select completion";
pub const name = "completion";
pub const description = "completions";
pub const icon = "󱎸 ";
pub const Entry = struct {
label: []const u8,
sort_text: []const u8,
cbor: []const u8,
};
pub fn load_entries(palette: *Type) !usize {
const editor = tui.get_active_editor() orelse return error.NotFound;
var iter: []const u8 = editor.completions.items;
while (iter.len > 0) {
var cbor_item: []const u8 = undefined;
if (!try cbor.matchValue(&iter, cbor.extract_cbor(&cbor_item))) return error.BadCompletion;
(try palette.entries.addOne()).* = .{ .cbor = cbor_item, .label = undefined, .sort_text = undefined };
}
var max_label_len: usize = 0;
for (palette.entries.items) |*item| {
const label_, const sort_text, _ = get_values(item.cbor);
item.label = label_;
item.sort_text = sort_text;
max_label_len = @max(max_label_len, item.label.len);
}
const less_fn = struct {
fn less_fn(_: void, lhs: Entry, rhs: Entry) bool {
const lhs_str = if (lhs.sort_text.len > 0) lhs.sort_text else lhs.label;
const rhs_str = if (rhs.sort_text.len > 0) rhs.sort_text else rhs.label;
return std.mem.order(u8, lhs_str, rhs_str) == .lt;
}
}.less_fn;
std.mem.sort(Entry, palette.entries.items, {}, less_fn);
return if (max_label_len > label.len + 3) 0 else label.len + 3 - max_label_len;
}
pub fn clear_entries(palette: *Type) void {
palette.entries.clearRetainingCapacity();
}
pub fn add_menu_entry(palette: *Type, entry: *Entry, matches: ?[]const usize) !void {
var value = std.ArrayList(u8).init(palette.allocator);
defer value.deinit();
const writer = value.writer();
try writer.writeAll(entry.cbor);
try cbor.writeValue(writer, matches orelse &[_]usize{});
try palette.menu.add_item_with_handler(value.items, select);
palette.items += 1;
}
pub fn on_render_menu(_: *Type, button: *Type.ButtonState, theme: *const Widget.Theme, selected: bool) bool {
var item_cbor: []const u8 = undefined;
var matches_cbor: []const u8 = undefined;
var iter = button.opts.label;
if (!(cbor.matchValue(&iter, cbor.extract_cbor(&item_cbor)) catch false)) return false;
if (!(cbor.matchValue(&iter, cbor.extract_cbor(&matches_cbor)) catch false)) return false;
const label_, _, const kind = get_values(item_cbor);
const icon_: []const u8 = kind_icon(@enumFromInt(kind));
const color: u24 = 0x0;
const indicator: []const u8 = &.{};
return tui.render_file_item(&button.plane, label_, icon_, color, indicator, matches_cbor, button.active, selected, button.hover, theme);
}
fn get_values(item_cbor: []const u8) struct { []const u8, []const u8, u8 } {
var label_: []const u8 = "";
var sort_text: []const u8 = "";
var kind: u8 = 0;
_ = cbor.match(item_cbor, .{
cbor.any, // file_path
cbor.any, // row
cbor.any, // col
cbor.any, // is_incomplete
cbor.extract(&label_), // label
cbor.any, // label_detail
cbor.any, // label_description
cbor.extract(&kind), // kind
cbor.any, // detail
cbor.any, // documentation
cbor.any, // documentation_kind
cbor.extract(&sort_text), // sortText
cbor.any, // insertTextFormat
cbor.any, // textEdit_newText
cbor.any, // insert.begin.row
cbor.any, // insert.begin.col
cbor.any, // insert.end.row
cbor.any, // insert.end.col
cbor.any, // replace.begin.row
cbor.any, // replace.begin.col
cbor.any, // replace.end.row
cbor.any, // replace.end.col
}) catch false;
return .{ label_, sort_text, kind };
}
fn select(menu: **Type.MenuState, button: *Type.ButtonState) void {
const label_, _, _ = get_values(button.opts.label);
tp.self_pid().send(.{ "cmd", "exit_overlay_mode" }) catch |e| menu.*.opts.ctx.logger.err(module_name, e);
tp.self_pid().send(.{ "cmd", "insert_chars", .{label_} }) catch |e| menu.*.opts.ctx.logger.err(module_name, e);
}
const CompletionItemKind = enum(u8) {
Text = 1,
Method = 2,
Function = 3,
Constructor = 4,
Field = 5,
Variable = 6,
Class = 7,
Interface = 8,
Module = 9,
Property = 10,
Unit = 11,
Value = 12,
Enum = 13,
Keyword = 14,
Snippet = 15,
Color = 16,
File = 17,
Reference = 18,
Folder = 19,
EnumMember = 20,
Constant = 21,
Struct = 22,
Event = 23,
Operator = 24,
TypeParameter = 25,
};
fn kind_icon(kind: CompletionItemKind) []const u8 {
return switch (kind) {
.Text => "󰊄",
.Method => "",
.Function => "󰊕",
.Constructor => "",
.Field => "",
.Variable => "",
.Class => "",
.Interface => "",
.Module => "",
.Property => "",
.Unit => "󱔁",
.Value => "󱔁",
.Enum => "",
.Keyword => "",
.Snippet => "",
.Color => "",
.File => "",
.Reference => "",
.Folder => "🗀",
.EnumMember => "",
.Constant => "",
.Struct => "",
.Event => "",
.Operator => "",
.TypeParameter => "",
};
}

View file

@ -14,6 +14,7 @@ pub fn Variant(comptime command: []const u8, comptime label_: []const u8, allow_
pub const label = label_;
pub const name = " file type";
pub const description = "file type";
pub const icon = "";
pub const Entry = struct {
label: []const u8,
@ -84,20 +85,18 @@ pub fn Variant(comptime command: []const u8, comptime label_: []const u8, allow_
}
button.plane.set_style(style_hint);
const pointer = if (selected) "" else " ";
_ = button.plane.print("{s}", .{pointer}) catch {};
tui.render_pointer(&button.plane, selected);
var iter = button.opts.label;
var description_: []const u8 = undefined;
var icon: []const u8 = undefined;
var icon_: []const u8 = undefined;
var color: u24 = undefined;
if (!(cbor.matchString(&iter, &description_) catch false)) @panic("invalid file_type description");
if (!(cbor.matchString(&iter, &icon) catch false)) @panic("invalid file_type icon");
if (!(cbor.matchString(&iter, &icon_) catch false)) @panic("invalid file_type icon");
if (!(cbor.matchInt(u24, &iter, &color) catch false)) @panic("invalid file_type color");
if (tui.config().show_fileicons) {
tui.render_file_icon(&button.plane, icon, color);
_ = button.plane.print(" ", .{}) catch {};
}
const icon_width = tui.render_file_icon(&button.plane, icon_, color);
button.plane.set_style(style_label);
_ = button.plane.print("{s} ", .{description_}) catch {};
@ -111,7 +110,7 @@ pub fn Variant(comptime command: []const u8, comptime label_: []const u8, allow_
var len = cbor.decodeArrayHeader(&iter) catch return false;
while (len > 0) : (len -= 1) {
if (cbor.matchValue(&iter, cbor.extract(&index)) catch break) {
tui.render_match_cell(&button.plane, 0, index + 4, theme) catch break;
tui.render_match_cell(&button.plane, 0, index + 2 + icon_width, theme) catch break;
} else break;
}
return false;
@ -119,12 +118,12 @@ pub fn Variant(comptime command: []const u8, comptime label_: []const u8, allow_
fn select(menu: **Type.MenuState, button: *Type.ButtonState) void {
var description_: []const u8 = undefined;
var icon: []const u8 = undefined;
var icon_: []const u8 = undefined;
var color: u24 = undefined;
var name_: []const u8 = undefined;
var iter = button.opts.label;
if (!(cbor.matchString(&iter, &description_) catch false)) return;
if (!(cbor.matchString(&iter, &icon) catch false)) return;
if (!(cbor.matchString(&iter, &icon_) catch false)) return;
if (!(cbor.matchInt(u24, &iter, &color) catch false)) return;
if (!(cbor.matchString(&iter, &name_) catch false)) return;
if (!allow_previous) if (previous_file_type) |prev| if (std.mem.eql(u8, prev, name_))

View file

@ -2,6 +2,7 @@ const std = @import("std");
const tp = @import("thespian");
const log = @import("log");
const cbor = @import("cbor");
const file_type_config = @import("file_type_config");
const root = @import("root");
const Plane = @import("renderer").Plane;
@ -22,6 +23,7 @@ const ModalBackground = @import("../../ModalBackground.zig");
const Self = @This();
const max_recent_files: usize = 25;
const widget_type: Widget.Type = .palette;
allocator: std.mem.Allocator,
f: usize = 0,
@ -32,7 +34,7 @@ logger: log.Logger,
query_pending: bool = false,
need_reset: bool = false,
need_select_first: bool = true,
longest: usize = 0,
longest: usize,
commands: Commands = undefined,
buffer_manager: ?*BufferManager,
@ -47,21 +49,25 @@ pub fn create(allocator: std.mem.Allocator) !tui.Mode {
.modal = try ModalBackground.create(*Self, allocator, tui.mainview_widget(), .{ .ctx = self }),
.menu = try Menu.create(*Self, allocator, tui.plane(), .{
.ctx = self,
.style = widget_type,
.on_render = on_render_menu,
.on_resize = on_resize_menu,
.prepare_resize = prepare_resize_menu,
}),
.logger = log.logger(@typeName(Self)),
.inputbox = (try self.menu.add_header(try InputBox.create(*Self, self.allocator, self.menu.menu.parent, .{
.ctx = self,
.label = inputbox_label,
.padding = 2,
.icon = "󰈞 ",
}))).dynamic_cast(InputBox.State(*Self)) orelse unreachable,
.buffer_manager = tui.get_buffer_manager(),
.longest = inputbox_label.len,
};
try self.commands.init(self);
try tui.message_filters().add(MessageFilter.bind(self, receive_project_manager));
self.query_pending = true;
try project_manager.request_recent_files(max_recent_files);
self.menu.resize(.{ .y = 0, .x = self.menu_pos_x(), .w = max_menu_width() + 2 });
self.do_resize();
try mv.floating_views.add(self.modal.widget());
try mv.floating_views.add(self.menu.container_widget);
var mode = try keybind.mode("overlay/palette", allocator, .{
@ -84,7 +90,7 @@ pub fn deinit(self: *Self) void {
}
inline fn menu_width(self: *Self) usize {
return @max(@min(self.longest, max_menu_width()) + 2, inputbox_label.len + 2);
return @max(@min(self.longest + 3, max_menu_width()) + 5, inputbox_label.len + 2);
}
inline fn menu_pos_x(self: *Self) usize {
@ -98,54 +104,23 @@ inline fn max_menu_width() usize {
return @max(15, width - (width / 5));
}
fn on_render_menu(self: *Self, button: *Button.State(*Menu.State(*Self)), theme: *const Widget.Theme, selected: bool) bool {
const style_base = theme.editor_widget;
const style_label = if (button.active) theme.editor_cursor else if (button.hover or selected) theme.editor_selection else theme.editor_widget;
const style_keybind = if (tui.find_scope_style(theme, "entity.name")) |sty| sty.style else style_base;
button.plane.set_base_style(style_base);
button.plane.erase();
button.plane.home();
button.plane.set_style(style_label);
if (button.active or button.hover or selected) {
button.plane.fill(" ");
button.plane.home();
}
var file_path: []const u8 = undefined;
var iter = button.opts.label; // label contains cbor, first the file name, then multiple match indexes
if (!(cbor.matchString(&iter, &file_path) catch false))
file_path = "#ERROR#";
button.plane.set_style(style_keybind);
const dirty = if (self.buffer_manager) |bm| if (bm.is_buffer_dirty(file_path)) "" else " " else " ";
const pointer = if (selected) "" else dirty;
_ = button.plane.print("{s}", .{pointer}) catch {};
var buf: [std.fs.max_path_bytes]u8 = undefined;
var removed_prefix: usize = 0;
const max_len = max_menu_width() - 2;
button.plane.set_style(style_label);
_ = button.plane.print("{s} ", .{
if (file_path.len > max_len) root.shorten_path(&buf, file_path, &removed_prefix, max_len) else file_path,
}) catch {};
var index: usize = 0;
var len = cbor.decodeArrayHeader(&iter) catch return false;
while (len > 0) : (len -= 1) {
if (cbor.matchValue(&iter, cbor.extract(&index)) catch break) {
const cell_idx = if (index < removed_prefix) 1 else index + 1 - removed_prefix;
render_cell(&button.plane, 0, cell_idx, theme.editor_match) catch break;
} else break;
}
return false;
fn on_render_menu(_: *Self, button: *Button.State(*Menu.State(*Self)), theme: *const Widget.Theme, selected: bool) bool {
return tui.render_file_item_cbor(&button.plane, button.opts.label, button.active, selected, button.hover, theme);
}
fn render_cell(plane: *Plane, y: usize, x: usize, style: Widget.Theme.Style) !void {
plane.cursor_move_yx(@intCast(y), @intCast(x)) catch return;
var cell = plane.cell_init();
_ = plane.at_cursor_cell(&cell) catch return;
cell.set_style(style);
_ = plane.putc(&cell) catch {};
fn prepare_resize_menu(self: *Self, _: *Menu.State(*Self), _: Widget.Box) Widget.Box {
return self.prepare_resize();
}
fn on_resize_menu(self: *Self, _: *Menu.State(*Self), _: Widget.Box) void {
self.menu.resize(.{ .y = 0, .x = self.menu_pos_x(), .w = self.menu_width() });
fn prepare_resize(self: *Self) Widget.Box {
const w = self.menu_width();
const x = self.menu_pos_x();
const h = self.menu.menu.widgets.items.len;
return .{ .y = 0, .x = x, .w = w, .h = h };
}
fn do_resize(self: *Self) void {
self.menu.resize(self.prepare_resize());
}
fn menu_action_open_file(menu: **Menu.State(*Self), button: *Button.State(*Menu.State(*Self))) void {
@ -156,12 +131,22 @@ fn menu_action_open_file(menu: **Menu.State(*Self), button: *Button.State(*Menu.
tp.self_pid().send(.{ "cmd", "navigate", .{ .file = file_path } }) catch |e| menu.*.opts.ctx.logger.err("navigate", e);
}
fn add_item(self: *Self, file_name: []const u8, matches: ?[]const u8) !void {
fn add_item(
self: *Self,
file_name: []const u8,
file_icon: []const u8,
file_color: u24,
indicator: []const u8,
matches: ?[]const u8,
) !void {
var label = std.ArrayList(u8).init(self.allocator);
defer label.deinit();
const writer = label.writer();
try cbor.writeValue(writer, file_name);
if (matches) |cb| _ = try writer.write(cb);
try cbor.writeValue(writer, file_icon);
try cbor.writeValue(writer, file_color);
try cbor.writeValue(writer, indicator);
if (matches) |cb| _ = try writer.write(cb) else try cbor.writeValue(writer, &[_]usize{});
try self.menu.add_item_with_handler(label.items, menu_action_open_file);
}
@ -175,21 +160,43 @@ fn receive_project_manager(self: *Self, _: tp.pid_ref, m: tp.message) MessageFil
fn process_project_manager(self: *Self, m: tp.message) MessageFilter.Error!void {
var file_name: []const u8 = undefined;
var file_type: []const u8 = undefined;
var file_icon: []const u8 = undefined;
var file_color: u24 = undefined;
var matches: []const u8 = undefined;
var query: []const u8 = undefined;
if (try cbor.match(m.buf, .{ "PRJ", "recent", tp.extract(&self.longest), tp.extract(&file_name), tp.extract_cbor(&matches) })) {
if (try cbor.match(m.buf, .{
"PRJ",
"recent",
tp.extract(&self.longest),
tp.extract(&file_name),
tp.extract(&file_type),
tp.extract(&file_icon),
tp.extract(&file_color),
tp.extract_cbor(&matches),
})) {
if (self.need_reset) self.reset_results();
try self.add_item(file_name, matches);
self.menu.resize(.{ .y = 0, .x = self.menu_pos_x(), .w = self.menu_width() });
const indicator = if (self.buffer_manager) |bm| tui.get_file_state_indicator(bm, file_name) else "";
try self.add_item(file_name, file_icon, file_color, indicator, matches);
self.do_resize();
if (self.need_select_first) {
self.menu.select_down();
self.need_select_first = false;
}
tui.need_render();
} else if (try cbor.match(m.buf, .{ "PRJ", "recent", tp.extract(&self.longest), tp.extract(&file_name) })) {
} else if (try cbor.match(m.buf, .{
"PRJ",
"recent",
tp.extract(&self.longest),
tp.extract(&file_name),
tp.extract(&file_type),
tp.extract(&file_icon),
tp.extract(&file_color),
})) {
if (self.need_reset) self.reset_results();
try self.add_item(file_name, null);
self.menu.resize(.{ .y = 0, .x = self.menu_pos_x(), .w = self.menu_width() });
const indicator = if (self.buffer_manager) |bm| tui.get_file_state_indicator(bm, file_name) else "";
try self.add_item(file_name, file_icon, file_color, indicator, null);
self.do_resize();
if (self.need_select_first) {
self.menu.select_down();
self.need_select_first = false;
@ -363,6 +370,14 @@ const cmds = struct {
}
pub const overlay_toggle_inputview_meta: Meta = .{};
pub fn overlay_next_widget_style(self: *Self, _: Ctx) Result {
tui.set_next_style(widget_type);
self.do_resize();
tui.need_render();
try tui.save_config();
}
pub const overlay_next_widget_style_meta: Meta = .{};
pub fn mini_mode_paste(self: *Self, ctx: Ctx) Result {
return overlay_insert_bytes(self, ctx);
}

View file

@ -27,10 +27,12 @@ pub fn deinit(palette: *Type) void {
palette.allocator.free(entry.label);
}
pub fn load_entries(palette: *Type) !usize {
const rsp = try project_manager.request_recent_projects(palette.allocator);
defer palette.allocator.free(rsp.buf);
var iter: []const u8 = rsp.buf;
pub fn load_entries_with_args(palette: *Type, ctx: command.Context) !usize {
var items_cbor: []const u8 = undefined;
if (!(cbor.match(ctx.args.buf, .{ "PRJ", "recent_projects", tp.extract_cbor(&items_cbor) }) catch false))
return error.InvalidRecentProjects;
var iter: []const u8 = items_cbor;
var len = try cbor.decodeArrayHeader(&iter);
while (len > 0) : (len -= 1) {
var name_: []const u8 = undefined;

View file

@ -20,6 +20,7 @@ const ModalBackground = @import("../../ModalBackground.zig");
pub const Menu = @import("../../Menu.zig");
const max_menu_width = 80;
const widget_type: Widget.Type = .palette;
pub fn Create(options: type) type {
return struct {
@ -46,6 +47,10 @@ pub fn Create(options: type) type {
pub const ButtonState = Button.State(*Menu.State(*Self));
pub fn create(allocator: std.mem.Allocator) !tui.Mode {
return create_with_args(allocator, .{});
}
pub fn create_with_args(allocator: std.mem.Allocator, ctx: command.Context) !tui.Mode {
const mv = tui.mainview() orelse return error.NotFound;
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
@ -57,8 +62,10 @@ pub fn Create(options: type) type {
}),
.menu = try Menu.create(*Self, allocator, tui.plane(), .{
.ctx = self,
.style = widget_type,
.on_render = if (@hasDecl(options, "on_render_menu")) options.on_render_menu else on_render_menu,
.on_resize = on_resize_menu,
.prepare_resize = prepare_resize_menu,
.after_resize = after_resize_menu,
.on_scroll = EventHandler.bind(self, Self.on_scroll),
.on_click4 = mouse_click_button4,
.on_click5 = mouse_click_button5,
@ -67,12 +74,17 @@ pub fn Create(options: type) type {
.inputbox = (try self.menu.add_header(try InputBox.create(*Self, self.allocator, self.menu.menu.parent, .{
.ctx = self,
.label = options.label,
.padding = 2,
.icon = if (@hasDecl(options, "icon")) options.icon else null,
}))).dynamic_cast(InputBox.State(*Self)) orelse unreachable,
.view_rows = get_view_rows(tui.screen()),
.entries = std.ArrayList(Entry).init(allocator),
};
if (self.menu.scrollbar) |scrollbar| scrollbar.style_factory = scrollbar_style;
self.longest_hint = try options.load_entries(self);
self.longest_hint = if (@hasDecl(options, "load_entries_with_args"))
try options.load_entries_with_args(self, ctx)
else
try options.load_entries(self);
if (@hasDecl(options, "restore_state"))
options.restore_state(self) catch {};
try self.commands.init(self);
@ -130,8 +142,7 @@ pub fn Create(options: type) type {
if (!(cbor.matchString(&iter, &hint) catch false))
hint = "";
button.plane.set_style(style_hint);
const pointer = if (selected) "" else " ";
_ = button.plane.print("{s}", .{pointer}) catch {};
tui.render_pointer(&button.plane, selected);
button.plane.set_style(style_label);
_ = button.plane.print("{s} ", .{label}) catch {};
button.plane.set_style(style_hint);
@ -140,25 +151,38 @@ pub fn Create(options: type) type {
var len = cbor.decodeArrayHeader(&iter) catch return false;
while (len > 0) : (len -= 1) {
if (cbor.matchValue(&iter, cbor.extract(&index)) catch break) {
tui.render_match_cell(&button.plane, 0, index + 1, theme) catch break;
tui.render_match_cell(&button.plane, 0, index + 2, theme) catch break;
} else break;
}
return false;
}
fn on_resize_menu(self: *Self, _: *Menu.State(*Self), _: Widget.Box) void {
self.do_resize();
// self.start_query(0) catch {};
fn prepare_resize_menu(self: *Self, _: *Menu.State(*Self), _: Widget.Box) Widget.Box {
return self.prepare_resize();
}
fn do_resize(self: *Self) void {
fn prepare_resize(self: *Self) Widget.Box {
const screen = tui.screen();
const w = @max(@min(self.longest, max_menu_width) + 2 + 1 + self.longest_hint, options.label.len + 2);
const w = @max(@min(self.longest + 3, max_menu_width) + 2 + self.longest_hint, options.label.len + 2);
const x = if (screen.w > w) (screen.w - w) / 2 else 0;
self.view_rows = get_view_rows(screen);
const h = @min(self.items + self.menu.header_count, self.view_rows + self.menu.header_count);
self.menu.container.resize(.{ .y = 0, .x = x, .w = w, .h = h });
return .{ .y = 0, .x = x, .w = w, .h = h };
}
fn after_resize_menu(self: *Self, _: *Menu.State(*Self), _: Widget.Box) void {
return self.after_resize();
}
fn after_resize(self: *Self) void {
self.update_scrollbar();
// self.start_query(0) catch {};
}
fn do_resize(self: *Self, padding: Widget.Style.Margin) void {
const box = self.prepare_resize();
self.menu.resize(self.menu.container.to_client_box(box, padding));
self.after_resize();
}
fn get_view_rows(screen: Widget.Box) usize {
@ -239,7 +263,8 @@ pub fn Create(options: type) type {
var i = n;
while (i > 0) : (i -= 1)
self.menu.select_down();
self.do_resize();
const padding = tui.get_widget_style(widget_type).padding;
self.do_resize(padding);
tui.refresh_hover();
self.selection_updated();
}
@ -449,15 +474,22 @@ pub fn Create(options: type) type {
const button = self.menu.get_selected() orelse return;
const refresh = options.delete_item(self.menu, button);
if (refresh) {
options.clear_entries(self);
self.longest_hint = try options.load_entries(self);
if (self.entries.items.len > 0)
self.initial_selected = self.menu.selected;
try self.start_query(0);
if (@hasDecl(options, "load_entries")) {
options.clear_entries(self);
self.longest_hint = try options.load_entries(self);
if (self.entries.items.len > 0)
self.initial_selected = self.menu.selected;
try self.start_query(0);
} else {
return palette_menu_cancel(self, .{});
}
}
}
}
pub const palette_menu_delete_item_meta: Meta = .{};
pub const palette_menu_delete_item_meta: Meta = .{
.description = "Delete item",
.icon = "󰗨",
};
pub fn palette_menu_activate(self: *Self, _: Ctx) Result {
self.menu.activate_selected();
@ -511,6 +543,15 @@ pub fn Create(options: type) type {
}
pub const overlay_toggle_inputview_meta: Meta = .{};
pub fn overlay_next_widget_style(self: *Self, _: Ctx) Result {
tui.set_next_style(widget_type);
const padding = tui.get_widget_style(widget_type).padding;
self.do_resize(padding);
tui.need_render();
try tui.save_config();
}
pub const overlay_next_widget_style_meta: Meta = .{};
pub fn mini_mode_paste(self: *Self, ctx: Ctx) Result {
return overlay_insert_bytes(self, ctx);
}

View file

@ -33,10 +33,8 @@ pub fn load_entries(palette: *Type) !usize {
(try palette.entries.addOne()).* = .{ .label = try palette.allocator.dupe(u8, task) };
} else return error.InvalidTaskMessageField;
}
(try palette.entries.addOne()).* = .{
.label = try palette.allocator.dupe(u8, " Add new task"),
.command = "add_task",
};
(try palette.entries.addOne()).* = .{ .label = "", .command = "add_task" };
(try palette.entries.addOne()).* = .{ .label = "", .command = "palette_menu_delete_item" };
return if (palette.entries.items.len == 0) label.len else blk: {
var longest: usize = 0;
for (palette.entries.items) |item| longest = @max(longest, item.label.len);
@ -60,7 +58,7 @@ pub fn add_menu_entry(palette: *Type, entry: *Entry, matches: ?[]const usize) !v
palette.items += 1;
}
pub fn on_render_menu(_: *Type, button: *Type.ButtonState, theme: *const Widget.Theme, selected: bool) bool {
pub fn on_render_menu(palette: *Type, button: *Type.ButtonState, theme: *const Widget.Theme, selected: bool) bool {
var entry: Entry = undefined;
var iter = button.opts.label; // label contains cbor entry object and matches
if (!(cbor.matchValue(&iter, cbor.extract(&entry)) catch false))
@ -84,11 +82,30 @@ pub fn on_render_menu(_: *Type, button: *Type.ButtonState, theme: *const Widget.
button.plane.set_style(style_label);
button.plane.fill(" ");
button.plane.home();
button.plane.set_style(style_hint);
const pointer = if (selected) "" else " ";
_ = button.plane.print("{s}", .{pointer}) catch {};
tui.render_pointer(&button.plane, selected);
button.plane.set_style(style_label);
_ = button.plane.print("{s} ", .{entry.label}) catch {};
if (entry.command) |command_name| blk: {
button.plane.set_style(style_hint);
var label_: std.ArrayListUnmanaged(u8) = .empty;
defer label_.deinit(palette.allocator);
const id = command.get_id(command_name) orelse break :blk;
if (command.get_icon(id)) |icon|
label_.writer(palette.allocator).print("{s} ", .{icon}) catch {};
if (command.get_description(id)) |desc|
label_.writer(palette.allocator).print("{s}", .{desc}) catch {};
_ = button.plane.print("{s} ", .{label_.items}) catch {};
const hints = if (tui.input_mode()) |m| m.keybind_hints else @panic("no keybind hints");
if (hints.get(command_name)) |hint|
_ = button.plane.print_aligned_right(0, "{s} ", .{hint}) catch {};
} else {
_ = button.plane.print("{s} ", .{entry.label}) catch {};
}
var index: usize = 0;
var len = cbor.decodeArrayHeader(&iter) catch return false;
while (len > 0) : (len -= 1) {
@ -103,17 +120,13 @@ fn select(menu: **Type.MenuState, button: *Type.ButtonState) void {
var entry: Entry = undefined;
var iter = button.opts.label;
if (!(cbor.matchValue(&iter, cbor.extract(&entry)) catch false)) return;
var buffer_name = std.ArrayList(u8).init(menu.*.opts.ctx.allocator);
defer buffer_name.deinit();
buffer_name.writer().print("*{s}*", .{entry.label}) catch {};
if (entry.command) |cmd| {
if (entry.command) |command_name| {
tp.self_pid().send(.{ "cmd", "exit_overlay_mode" }) catch |e| menu.*.opts.ctx.logger.err(module_name, e);
tp.self_pid().send(.{ "cmd", cmd, .{entry.label} }) catch |e| menu.*.opts.ctx.logger.err(module_name, e);
tp.self_pid().send(.{ "cmd", command_name, .{} }) catch |e| menu.*.opts.ctx.logger.err(module_name, e);
} else {
project_manager.add_task(entry.label) catch {};
tp.self_pid().send(.{ "cmd", "exit_overlay_mode" }) catch |e| menu.*.opts.ctx.logger.err(module_name, e);
tp.self_pid().send(.{ "cmd", "create_scratch_buffer", .{ buffer_name.items, "", "conf" } }) catch |e| menu.*.opts.ctx.logger.err(module_name, e);
tp.self_pid().send(.{ "cmd", "shell_execute_stream", .{entry.label} }) catch |e| menu.*.opts.ctx.logger.err(module_name, e);
project_manager.add_task(entry.label) catch {};
tp.self_pid().send(.{ "cmd", "run_task", .{entry.label} }) catch |e| menu.*.opts.ctx.logger.err(module_name, e);
}
}

View file

@ -90,11 +90,11 @@ fn format(self: *Self) void {
const writer = fbs.writer();
const eol_mode = switch (self.eol_mode) {
.lf => "",
.crlf => " [␍␊]",
.crlf => " ␍␊",
};
const indent_mode = switch (self.indent_mode) {
.spaces, .auto => "",
.tabs => " [⭾]",
.tabs => " ",
};
std.fmt.format(writer, "{s}{s} Ln ", .{ eol_mode, indent_mode }) catch {};
self.format_count(writer, self.line + 1, self.padding orelse 0) catch {};

View file

@ -24,6 +24,7 @@ allocator: Allocator,
rdr_: renderer,
config_: @import("config"),
config_bufs: [][]const u8,
session_tab_width: ?usize = null,
highlight_columns_: []const u16,
highlight_columns_configured: []const u16,
frame_time: usize, // in microseconds
@ -103,6 +104,8 @@ const InitError = error{
keybind.LoadError;
fn init(allocator: Allocator) InitError!*Self {
log.stdout(.disable);
var conf, const conf_bufs = root.read_config(@import("config"), allocator);
if (@hasDecl(renderer, "install_crash_handler") and conf.start_debugger_on_crash)
@ -119,6 +122,8 @@ fn init(allocator: Allocator) InitError!*Self {
const frame_time = std.time.us_per_s / conf.frame_rate;
const frame_clock = try tp.metronome.init(frame_time);
tp.env.get().set("lsp_verbose", conf.lsp_output == .verbose);
var self = try allocator.create(Self);
// don't destroy
// if tui fails it is catastrophic anyway and we don't want to cause nock-on errors
@ -157,6 +162,8 @@ fn init(allocator: Allocator) InitError!*Self {
self.rdr_.dispatch_event = dispatch_event;
try self.rdr_.run();
log.stderr(.disable);
try project_manager.start();
try frame_clock.start();
@ -384,6 +391,9 @@ fn receive_safe(self: *Self, from: tp.pid_ref, m: tp.message) !void {
return;
}
if (try m.match(.{"mouse_leave"}))
return;
if (try m.match(.{"focus_in"}))
return;
@ -408,6 +418,9 @@ fn receive_safe(self: *Self, from: tp.pid_ref, m: tp.message) !void {
return;
}
if (try m.match(.{ "PRJ", "recent_projects", tp.more })) // async recent projects request
return self.enter_overlay_mode_with_args(@import("mode/overlay/open_recent_project.zig").Type, .{ .args = m });
if (try m.match(.{ "PRJ", tp.more })) // drop late project manager query responses
return;
@ -679,6 +692,17 @@ fn enter_overlay_mode(self: *Self, mode: type) command.Result {
refresh_hover();
}
fn enter_overlay_mode_with_args(self: *Self, mode: type, ctx: command.Context) command.Result {
command.executeName("disable_fast_scroll", .{}) catch {};
command.executeName("disable_jump_mode", .{}) catch {};
if (self.mini_mode_) |_| try cmds.exit_mini_mode(self, .{});
if (self.input_mode_outer_) |_| try cmds.exit_overlay_mode(self, .{});
self.input_mode_outer_ = self.input_mode_;
self.input_mode_ = try mode.create_with_args(self.allocator, ctx);
if (self.input_mode_) |*m| m.run_init();
refresh_hover();
}
fn get_input_mode(self: *Self, mode_name: []const u8) !Mode {
return keybind.mode(mode_name, self.allocator, .{});
}
@ -709,17 +733,22 @@ fn refresh_input_mode(self: *Self) command.Result {
if (self.input_mode_) |*m| m.run_init();
}
fn set_theme_by_name(self: *Self, name: []const u8) !void {
fn set_theme_by_name(self: *Self, name: []const u8, action: enum { none, store }) !void {
const old = self.parsed_theme;
defer if (old) |p| p.deinit();
self.theme_, self.parsed_theme = get_theme_by_name(self.allocator, name) orelse {
self.logger.print("theme not found: {s}", .{name});
return;
};
self.config_.theme = self.theme_.name;
self.set_terminal_style();
self.logger.print("theme: {s}", .{self.theme_.description});
try save_config();
switch (action) {
.none => {},
.store => {
self.config_.theme = self.theme_.name;
try save_config();
},
}
}
const cmds = struct {
@ -740,23 +769,69 @@ const cmds = struct {
}
pub const force_terminate_meta: Meta = .{ .description = "Force quit without saving" };
pub fn set_tab_width(self: *Self, ctx: Ctx) Result {
var tab_width: usize = 0;
if (!try ctx.args.match(.{tp.extract(&tab_width)}))
return enter_mini_mode(self, @import("mode/mini/tab_width.zig"), Ctx.fmt(.{"set_tab_width"}));
self.config_.tab_width = tab_width;
self.session_tab_width = null;
command.executeName("set_editor_tab_width", ctx) catch {};
try save_config();
self.logger.print("tab width {}", .{tab_width});
}
pub const set_tab_width_meta: Meta = .{
.description = "Set tab width",
.arguments = &.{.integer},
};
pub fn set_buffer_tab_width(self: *Self, ctx: Ctx) Result {
var tab_width: usize = 0;
if (!try ctx.args.match(.{tp.extract(&tab_width)}))
return enter_mini_mode(self, @import("mode/mini/tab_width.zig"), Ctx.fmt(.{"set_buffer_tab_width"}));
command.executeName("set_editor_tab_width", ctx) catch {};
self.logger.print("buffer tab width {}", .{tab_width});
}
pub const set_buffer_tab_width_meta: Meta = .{
.description = "Set tab width for current buffer",
.arguments = &.{.integer},
};
pub fn set_session_tab_width(self: *Self, ctx: Ctx) Result {
var tab_width: usize = 0;
if (!try ctx.args.match(.{tp.extract(&tab_width)}))
return enter_mini_mode(self, @import("mode/mini/tab_width.zig"), Ctx.fmt(.{"set_session_tab_width"}));
self.session_tab_width = tab_width;
command.executeName("set_editor_tab_width", ctx) catch {};
self.logger.print("session tab width {}", .{tab_width});
}
pub const set_session_tab_width_meta: Meta = .{
.description = "Set tab width for current session",
.arguments = &.{.integer},
};
pub fn set_theme(self: *Self, ctx: Ctx) Result {
var name: []const u8 = undefined;
if (!try ctx.args.match(.{tp.extract(&name)}))
return tp.exit_error(error.InvalidSetThemeArgument, null);
return self.set_theme_by_name(name);
if (try ctx.args.match(.{tp.extract(&name)}))
return self.set_theme_by_name(name, .store);
if (try ctx.args.match(.{ tp.extract(&name), "no_store" }))
return self.set_theme_by_name(name, .none);
return tp.exit_error(error.InvalidSetThemeArgument, null);
}
pub const set_theme_meta: Meta = .{ .arguments = &.{.string} };
pub fn theme_next(self: *Self, _: Ctx) Result {
const name = get_next_theme_by_name(self.theme_.name);
return self.set_theme_by_name(name);
return self.set_theme_by_name(name, .store);
}
pub const theme_next_meta: Meta = .{ .description = "Next color theme" };
pub fn theme_prev(self: *Self, _: Ctx) Result {
const name = get_prev_theme_by_name(self.theme_.name);
return self.set_theme_by_name(name);
return self.set_theme_by_name(name, .store);
}
pub const theme_prev_meta: Meta = .{ .description = "Previous color theme" };
@ -858,8 +933,8 @@ const cmds = struct {
}
pub const open_recent_meta: Meta = .{ .description = "Open recent" };
pub fn open_recent_project(self: *Self, _: Ctx) Result {
return self.enter_overlay_mode(@import("mode/overlay/open_recent_project.zig").Type);
pub fn open_recent_project(_: *Self, _: Ctx) Result {
try project_manager.request_recent_projects();
}
pub const open_recent_project_meta: Meta = .{ .description = "Open project" };
@ -868,12 +943,11 @@ const cmds = struct {
}
pub const switch_buffers_meta: Meta = .{ .description = "Switch buffers" };
pub fn select_task(self: *Self, _: Ctx) Result {
return self.enter_overlay_mode(@import("mode/overlay/task_palette.zig").Type);
}
pub const select_task_meta: Meta = .{ .description = "Run task" };
pub fn add_task(self: *Self, ctx: Ctx) Result {
var task: []const u8 = undefined;
if (try ctx.args.match(.{tp.extract(&task)}))
return call_add_task(task);
return enter_mini_mode(self, struct {
pub const Type = @import("mode/mini/buffer.zig").Create(@This());
pub const create = Type.create;
@ -881,17 +955,42 @@ const cmds = struct {
return @import("mode/overlay/task_palette.zig").name;
}
pub fn select(self_: *Type) void {
project_manager.add_task(self_.input.items) catch |e| {
const logger = log.logger("tui");
logger.err("add_task", e);
logger.deinit();
};
tp.self_pid().send(.{ "cmd", "run_task", .{self_.input.items} }) catch {};
command.executeName("exit_mini_mode", .{}) catch {};
command.executeName("select_task", .{}) catch {};
}
}, ctx);
}
pub const add_task_meta: Meta = .{ .description = "Add task" };
pub const add_task_meta: Meta = .{
.description = "Add new task",
.arguments = &.{.string},
.icon = "",
};
fn call_add_task(task: []const u8) void {
project_manager.add_task(task) catch |e| {
const logger = log.logger("tui");
logger.err("add_task", e);
logger.deinit();
};
}
pub fn run_task(self: *Self, ctx: Ctx) Result {
var task: []const u8 = undefined;
if (try ctx.args.match(.{tp.extract(&task)})) {
var buffer_name = std.ArrayList(u8).init(self.allocator);
defer buffer_name.deinit();
buffer_name.writer().print("*{s}*", .{task}) catch {};
call_add_task(task);
tp.self_pid().send(.{ "cmd", "create_scratch_buffer", .{ buffer_name.items, "", "conf" } }) catch |e| self.logger.err("task", e);
tp.self_pid().send(.{ "cmd", "shell_execute_stream", .{task} }) catch |e| self.logger.err("task", e);
} else {
return self.enter_overlay_mode(@import("mode/overlay/task_palette.zig").Type);
}
}
pub const run_task_meta: Meta = .{
.description = "Run a task",
.arguments = &.{.string},
};
pub fn delete_task(_: *Self, ctx: Ctx) Result {
var task: []const u8 = undefined;
@ -964,10 +1063,10 @@ const cmds = struct {
fn enter_mini_mode(self: *Self, comptime mode: anytype, ctx: Ctx) !void {
command.executeName("disable_fast_scroll", .{}) catch {};
command.executeName("disable_jump_mode", .{}) catch {};
const input_mode_, const mini_mode_ = try mode.create(self.allocator, ctx);
if (self.mini_mode_) |_| try exit_mini_mode(self, .{});
if (self.input_mode_outer_) |_| try exit_overlay_mode(self, .{});
if (self.input_mode_outer_ != null) @panic("exit_overlay_mode failed");
const input_mode_, const mini_mode_ = try mode.create(self.allocator, ctx);
self.input_mode_outer_ = self.input_mode_;
self.input_mode_ = input_mode_;
self.mini_mode_ = mini_mode_;
@ -1113,6 +1212,11 @@ pub fn config() *const @import("config") {
return &current().config_;
}
pub fn get_tab_width() usize {
const self = current();
return self.session_tab_width orelse self.config_.tab_width;
}
pub fn highlight_columns() []const u16 {
return current().highlight_columns_;
}
@ -1375,7 +1479,19 @@ pub fn message(comptime fmt: anytype, args: anytype) void {
tp.self_pid().send(.{ "message", std.fmt.bufPrint(&buf, fmt, args) catch @panic("too large") }) catch {};
}
pub fn render_file_icon(self: *renderer.Plane, icon: []const u8, color: u24) void {
const dirty_indicator = "";
const hidden_indicator = "-";
pub fn get_file_state_indicator(buffer_manager: *const @import("Buffer").Manager, file_name: []const u8) []const u8 {
return if (buffer_manager.get_buffer_for_file(file_name)) |buffer| get_buffer_state_indicator(buffer) else "";
}
pub fn get_buffer_state_indicator(buffer: *const @import("Buffer")) []const u8 {
return if (buffer.is_dirty()) dirty_indicator else if (buffer.is_hidden()) hidden_indicator else "";
}
pub fn render_file_icon(self: *renderer.Plane, icon: []const u8, color: u24) usize {
if (!config().show_fileicons) return 0;
var cell = self.cell_init();
_ = self.at_cursor_cell(&cell) catch return;
if (!(color == 0xFFFFFF or color == 0x000000 or color == 0x000001)) {
@ -1384,6 +1500,8 @@ pub fn render_file_icon(self: *renderer.Plane, icon: []const u8, color: u24) voi
_ = self.cell_load(&cell, icon) catch {};
_ = self.putc(&cell) catch {};
self.cursor_move_rel(0, 1) catch {};
_ = self.print(" ", .{}) catch {};
return 3;
}
pub fn render_match_cell(self: *renderer.Plane, y: usize, x: usize, theme_: *const Widget.Theme) !void {
@ -1394,6 +1512,74 @@ pub fn render_match_cell(self: *renderer.Plane, y: usize, x: usize, theme_: *con
_ = self.putc(&cell) catch {};
}
pub fn render_pointer(self: *renderer.Plane, selected: bool) void {
const pointer = if (selected) "" else " ";
_ = self.print("{s}", .{pointer}) catch {};
}
pub fn render_file_item(
self: *renderer.Plane,
file_path_: []const u8,
icon: []const u8,
color: u24,
indicator: []const u8,
matches_cbor: []const u8,
active: bool,
selected: bool,
hover: bool,
theme_: *const Widget.Theme,
) bool {
const style_base = theme_.editor_widget;
const style_label = if (active) theme_.editor_cursor else if (hover or selected) theme_.editor_selection else theme_.editor_widget;
const style_hint = if (find_scope_style(theme_, "entity.name")) |sty| sty.style else style_label;
self.set_base_style(style_base);
self.erase();
self.home();
self.set_style(style_label);
if (active or hover or selected) {
self.fill(" ");
self.home();
}
self.set_style(style_hint);
render_pointer(self, selected);
const icon_width = render_file_icon(self, icon, color);
self.set_style(style_label);
_ = self.print("{s} ", .{file_path_}) catch {};
self.set_style(style_hint);
_ = self.print_aligned_right(0, "{s} ", .{indicator}) catch {};
var iter = matches_cbor;
var index: usize = 0;
var len = cbor.decodeArrayHeader(&iter) catch return false;
while (len > 0) : (len -= 1) {
if (cbor.matchValue(&iter, cbor.extract(&index)) catch break) {
render_match_cell(self, 0, index + 2 + icon_width, theme_) catch break;
} else break;
}
return false;
}
pub fn render_file_item_cbor(self: *renderer.Plane, file_item_cbor: []const u8, active: bool, selected: bool, hover: bool, theme_: *const Widget.Theme) bool {
var iter = file_item_cbor;
var file_path_: []const u8 = undefined;
var icon: []const u8 = undefined;
var color: u24 = undefined;
var indicator: []const u8 = undefined;
var matches_cbor: []const u8 = undefined;
if (!(cbor.matchString(&iter, &file_path_) catch false)) @panic("invalid buffer file path");
if (!(cbor.matchString(&iter, &icon) catch false)) @panic("invalid buffer file type icon");
if (!(cbor.matchInt(u24, &iter, &color) catch false)) @panic("invalid buffer file type color");
if (!(cbor.matchString(&iter, &indicator) catch false)) indicator = "";
if (!(cbor.matchValue(&iter, cbor.extract_cbor(&matches_cbor)) catch false)) @panic("invalid matches cbor");
return render_file_item(self, file_path_, icon, color, indicator, matches_cbor, active, selected, hover, theme_);
}
fn get_or_create_theme_file(self: *Self, allocator: std.mem.Allocator) ![]const u8 {
const theme_name = self.theme_.name;
if (root.read_theme(allocator, theme_name)) |content| {
@ -1422,3 +1608,40 @@ fn load_theme_file_internal(allocator: std.mem.Allocator, theme_name: []const u8
defer allocator.free(json_str);
return try std.json.parseFromSlice(Widget.Theme, allocator, json_str, .{ .allocate = .alloc_always });
}
pub const WidgetType = @import("config").WidgetType;
pub const ConfigWidgetStyle = @import("config").WidgetStyle;
pub const WidgetStyle = @import("WidgetStyle.zig");
pub fn get_widget_style(widget_type: WidgetType) *const WidgetStyle {
const config_ = config();
return switch (widget_type) {
.none => WidgetStyle.from_tag(config_.widget_style),
.palette => WidgetStyle.from_tag(config_.palette_style),
.panel => WidgetStyle.from_tag(config_.panel_style),
.home => WidgetStyle.from_tag(config_.home_style),
};
}
pub fn set_next_style(widget_type: WidgetType) void {
const ref = widget_type_config_variable(widget_type);
ref.* = next_widget_style(ref.*);
const self = current();
self.logger.print("{s} style {s}", .{ @tagName(widget_type), @tagName(ref.*) });
}
fn next_widget_style(tag: ConfigWidgetStyle) ConfigWidgetStyle {
const max_tag = comptime std.meta.tags(ConfigWidgetStyle).len;
const new_value = @intFromEnum(tag) + 1;
return if (new_value >= max_tag) @enumFromInt(0) else @enumFromInt(new_value);
}
fn widget_type_config_variable(widget_type: WidgetType) *ConfigWidgetStyle {
const config_ = config_mut();
return switch (widget_type) {
.none => &config_.widget_style,
.palette => &config_.palette_style,
.panel => &config_.panel_style,
.home => &config_.home_style,
};
}