Merge branch 'master' into zig-0.15.0
This commit is contained in:
commit
55a862eac0
47 changed files with 1610 additions and 2504 deletions
|
@ -5,7 +5,10 @@
|
|||
.fingerprint = 0x52c0d670590aa80f,
|
||||
|
||||
.dependencies = .{
|
||||
.syntax = .{ .path = "src/syntax" },
|
||||
.syntax = .{
|
||||
.url = "git+https://github.com/neurocyte/flow-syntax?ref=zig-0.14#410d19e633f237cd1602175450bd7d3bb03a1898",
|
||||
.hash = "flow_syntax-0.1.0-X8jOoT4OAQDibKKzYlJls3u5KczVh__cWYN7vTqCE1o3",
|
||||
},
|
||||
.flags = .{
|
||||
.url = "https://github.com/n0s4/flags/archive/372501d1576b5723829bcba98e41361132c7b618.tar.gz",
|
||||
.hash = "flags-0.8.0-AAAAAJV0AACuGBBnpUnHqZzAhoGTp4ibFROBQQQZGRqx",
|
||||
|
|
88
src/LSP.zig
88
src/LSP.zig
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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"]
|
||||
]
|
||||
|
|
31
src/log.zig
31
src/log.zig
|
@ -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 {
|
||||
|
|
83
src/main.zig
83
src/main.zig
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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"}));
|
||||
},
|
||||
|
|
1
src/syntax/.gitignore
vendored
1
src/syntax/.gitignore
vendored
|
@ -1 +0,0 @@
|
|||
/.zig-cache/
|
|
@ -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.
|
|
@ -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)
|
|
@ -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);
|
||||
}
|
|
@ -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",
|
||||
},
|
||||
}
|
|
@ -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 };
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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",
|
||||
};
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
};
|
|
@ -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;
|
||||
}
|
|
@ -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 };
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -14,6 +14,7 @@ pub const Error = (cbor.Error || cbor.JsonEncodeError || error{
|
|||
ThespianSpawnFailed,
|
||||
NoProject,
|
||||
ProjectManagerFailed,
|
||||
InvalidProjectDirectory,
|
||||
SendFailed,
|
||||
});
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
141
src/tui/WidgetStyle.zig
Normal 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 },
|
||||
};
|
||||
}
|
|
@ -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,6 +4493,7 @@ 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);
|
||||
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) {
|
||||
|
@ -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_);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 ""))
|
||||
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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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} };
|
||||
};
|
||||
|
|
148
src/tui/mode/mini/numeric_input.zig
Normal file
148
src/tui/mode/mini/numeric_input.zig
Normal 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" };
|
||||
};
|
||||
};
|
||||
}
|
32
src/tui/mode/mini/tab_width.zig
Normal file
32
src/tui/mode/mini/tab_width.zig
Normal 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 {};
|
||||
}
|
|
@ -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 {
|
||||
|
|
175
src/tui/mode/overlay/completion_palette.zig
Normal file
175
src/tui/mode/overlay/completion_palette.zig
Normal 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 => "",
|
||||
};
|
||||
}
|
|
@ -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_))
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {};
|
||||
|
|
263
src/tui/tui.zig
263
src/tui/tui.zig
|
@ -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});
|
||||
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)}))
|
||||
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);
|
||||
return self.set_theme_by_name(name);
|
||||
}
|
||||
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| {
|
||||
tp.self_pid().send(.{ "cmd", "run_task", .{self_.input.items} }) catch {};
|
||||
command.executeName("exit_mini_mode", .{}) catch {};
|
||||
}
|
||||
}, ctx);
|
||||
}
|
||||
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();
|
||||
};
|
||||
command.executeName("exit_mini_mode", .{}) catch {};
|
||||
command.executeName("select_task", .{}) catch {};
|
||||
}
|
||||
}, ctx);
|
||||
|
||||
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 add_task_meta: Meta = .{ .description = "Add task" };
|
||||
}
|
||||
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 ¤t().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,
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue