Merge branch 'master' into zig-0.15.0
This commit is contained in:
commit
4e80bae8b8
24 changed files with 701 additions and 262 deletions
|
@ -230,6 +230,7 @@ pub fn restore_state_v0(self: *Self, data: []const u8) error{
|
|||
InvalidPIntType,
|
||||
JsonIncompatibleType,
|
||||
NotAnObject,
|
||||
BadArrayAllocExtract,
|
||||
}!void {
|
||||
tp.trace(tp.channel.debug, .{"restore_state_v0"});
|
||||
defer self.sort_files_by_mtime();
|
||||
|
|
|
@ -23,10 +23,51 @@ pub const RGB = struct {
|
|||
return .{ .r = v[0], .g = v[1], .b = v[2] };
|
||||
}
|
||||
|
||||
pub fn from_string(s: []const u8) ?RGB {
|
||||
const nib = struct {
|
||||
fn f(c: u8) ?u8 {
|
||||
return switch (c) {
|
||||
'0'...'9' => c - '0',
|
||||
'A'...'F' => c - 'A' + 10,
|
||||
'a'...'f' => c - 'a' + 10,
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
}.f;
|
||||
|
||||
if (s.len != 7) return null;
|
||||
if (s[0] != '#') return null;
|
||||
const r = (nib(s[1]) orelse return null) << 4 | (nib(s[2]) orelse return null);
|
||||
const g = (nib(s[3]) orelse return null) << 4 | (nib(s[4]) orelse return null);
|
||||
const b = (nib(s[5]) orelse return null) << 4 | (nib(s[6]) orelse return null);
|
||||
return .{ .r = r, .g = g, .b = b };
|
||||
}
|
||||
|
||||
pub fn to_u8s(v: RGB) [3]u8 {
|
||||
return [_]u8{ v.r, v.g, v.b };
|
||||
}
|
||||
|
||||
pub fn to_string(v: RGB, s: *[7]u8) []u8 {
|
||||
const nib = struct {
|
||||
fn f(n: u8) u8 {
|
||||
return switch (n) {
|
||||
0...9 => '0' + n,
|
||||
0xA...0xF => 'A' + n - 10,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
}.f;
|
||||
|
||||
s[0] = '#';
|
||||
s[1] = nib(v.r >> 4);
|
||||
s[2] = nib(v.r & 0b00001111);
|
||||
s[3] = nib(v.g >> 4);
|
||||
s[4] = nib(v.g & 0b00001111);
|
||||
s[5] = nib(v.b >> 4);
|
||||
s[6] = nib(v.b & 0b00001111);
|
||||
return s;
|
||||
}
|
||||
|
||||
pub fn contrast(a_: RGB, b_: RGB) f32 {
|
||||
const a = RGBf.from_RGB(a_).luminance();
|
||||
const b = RGBf.from_RGB(b_).luminance();
|
||||
|
|
202
src/file_type_config.zig
Normal file
202
src/file_type_config.zig
Normal file
|
@ -0,0 +1,202 @@
|
|||
name: []const u8 = default.name,
|
||||
description: ?[]const u8 = null,
|
||||
extensions: ?[]const []const u8 = null,
|
||||
icon: ?[]const u8 = null,
|
||||
color: ?u24 = null,
|
||||
comment: ?[]const u8 = null,
|
||||
parser: ?[]const u8 = null,
|
||||
formatter: ?[]const []const u8 = null,
|
||||
language_server: ?[]const []const u8 = null,
|
||||
first_line_matches_prefix: ?[]const u8 = null,
|
||||
first_line_matches_content: ?[]const u8 = null,
|
||||
first_line_matches: ?[]const u8 = null,
|
||||
|
||||
include_files: []const u8 = "",
|
||||
|
||||
pub const default = struct {
|
||||
pub const name = "text";
|
||||
pub const description = "Plain Text";
|
||||
pub const icon = "🖹";
|
||||
pub const color = 0x000000;
|
||||
};
|
||||
|
||||
fn from_file_type(file_type: syntax.FileType) @This() {
|
||||
return .{
|
||||
.name = file_type.name,
|
||||
.color = file_type.color,
|
||||
.icon = file_type.icon,
|
||||
.description = file_type.description,
|
||||
.extensions = file_type.extensions,
|
||||
.first_line_matches_prefix = if (file_type.first_line_matches) |flm| flm.prefix else null,
|
||||
.first_line_matches_content = if (file_type.first_line_matches) |flm| flm.content else null,
|
||||
.parser = file_type.name,
|
||||
.comment = file_type.comment,
|
||||
.formatter = file_type.formatter,
|
||||
.language_server = file_type.language_server,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get_default(allocator: std.mem.Allocator, file_type_name: []const u8) ![]const u8 {
|
||||
const file_type = syntax.FileType.get_by_name_static(file_type_name) orelse return error.UnknownFileType;
|
||||
const config = from_file_type(file_type);
|
||||
var content = std.ArrayListUnmanaged(u8).empty;
|
||||
defer content.deinit(allocator);
|
||||
root.write_config_to_writer(@This(), config, content.writer(allocator)) catch {};
|
||||
return content.toOwnedSlice(allocator);
|
||||
}
|
||||
|
||||
pub fn get_all_names() []const []const u8 {
|
||||
cache_mutex.lock();
|
||||
defer cache_mutex.unlock();
|
||||
if (cache_list.len == 0)
|
||||
cache_list = load_all(cache_allocator) catch &.{};
|
||||
return cache_list;
|
||||
}
|
||||
|
||||
const cache_allocator = std.heap.c_allocator;
|
||||
var cache_mutex: std.Thread.Mutex = .{};
|
||||
var cache: CacheType = .empty;
|
||||
const CacheType = std.StringHashMapUnmanaged(?@This());
|
||||
var cache_list: []const []const u8 = &.{};
|
||||
|
||||
pub fn get(file_type_name: []const u8) !?@This() {
|
||||
cache_mutex.lock();
|
||||
defer cache_mutex.unlock();
|
||||
|
||||
return if (cache.get(file_type_name)) |self| self else self: {
|
||||
const file_type = file_type: {
|
||||
const file_name = try get_config_file_path(cache_allocator, file_type_name);
|
||||
defer cache_allocator.free(file_name);
|
||||
|
||||
const file: ?std.fs.File = std.fs.openFileAbsolute(file_name, .{ .mode = .read_only }) catch null;
|
||||
if (file) |f| {
|
||||
defer f.close();
|
||||
const stat = try f.stat();
|
||||
const buf = try cache_allocator.alloc(u8, @intCast(stat.size));
|
||||
defer cache_allocator.free(buf);
|
||||
const size = try f.readAll(buf);
|
||||
std.debug.assert(size == stat.size);
|
||||
var self: @This() = .{};
|
||||
var bufs_: [][]const u8 = &.{}; // cached, no need to free
|
||||
try root.parse_text_config_file(@This(), cache_allocator, &self, &bufs_, file_name, buf);
|
||||
break :file_type self;
|
||||
} else {
|
||||
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);
|
||||
break :self file_type;
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get_config_file_path(allocator: std.mem.Allocator, file_type: []const u8) ![]u8 {
|
||||
var stream = std.ArrayList(u8).fromOwnedSlice(allocator, try get_config_dir_path(allocator));
|
||||
const writer = stream.writer();
|
||||
_ = try writer.writeAll(file_type);
|
||||
_ = try writer.writeAll(".conf");
|
||||
return stream.toOwnedSlice();
|
||||
}
|
||||
|
||||
fn get_config_dir_path(allocator: std.mem.Allocator) ![]u8 {
|
||||
var stream = std.ArrayList(u8).init(allocator);
|
||||
const writer = stream.writer();
|
||||
_ = try writer.writeAll(try root.get_config_dir());
|
||||
_ = try writer.writeByte(std.fs.path.sep);
|
||||
_ = try writer.writeAll("file_type");
|
||||
_ = try writer.writeByte(std.fs.path.sep);
|
||||
std.fs.makeDirAbsolute(stream.items) catch |e| switch (e) {
|
||||
error.PathAlreadyExists => {},
|
||||
else => return e,
|
||||
};
|
||||
return stream.toOwnedSlice();
|
||||
}
|
||||
|
||||
const extension = ".conf";
|
||||
|
||||
fn load_all(allocator: std.mem.Allocator) ![]const []const u8 {
|
||||
const dir_path = try get_config_dir_path(allocator);
|
||||
defer allocator.free(dir_path);
|
||||
|
||||
var dir = try std.fs.cwd().openDir(dir_path, .{ .iterate = true });
|
||||
defer dir.close();
|
||||
|
||||
var names: std.StringHashMapUnmanaged(void) = .empty;
|
||||
defer names.deinit(allocator);
|
||||
|
||||
var iter = dir.iterate();
|
||||
while (try iter.next()) |entry| {
|
||||
if (entry.kind != .file) continue;
|
||||
if (!std.mem.endsWith(u8, entry.name, extension)) continue;
|
||||
const file_type_name = entry.name[0 .. entry.name.len - extension.len];
|
||||
if (!names.contains(file_type_name))
|
||||
try names.put(allocator, try allocator.dupe(u8, file_type_name), {});
|
||||
}
|
||||
|
||||
for (syntax.FileType.get_all()) |file_type| {
|
||||
if (!names.contains(file_type.name))
|
||||
try names.put(allocator, try allocator.dupe(u8, file_type.name), {});
|
||||
}
|
||||
|
||||
var list: std.ArrayListUnmanaged([]const u8) = .empty;
|
||||
defer list.deinit(allocator);
|
||||
|
||||
var names_iter = names.keyIterator();
|
||||
while (names_iter.next()) |key| {
|
||||
(try list.addOne(allocator)).* = key.*;
|
||||
}
|
||||
|
||||
const less_fn = struct {
|
||||
fn less_fn(_: void, lhs: []const u8, rhs: []const u8) bool {
|
||||
return std.mem.order(u8, lhs, rhs) == .lt;
|
||||
}
|
||||
}.less_fn;
|
||||
std.mem.sort([]const u8, list.items, {}, less_fn);
|
||||
|
||||
return list.toOwnedSlice(allocator);
|
||||
}
|
||||
|
||||
pub fn guess_file_type(file_path: ?[]const u8, content: []const u8) ?@This() {
|
||||
return guess(file_path, content);
|
||||
}
|
||||
|
||||
fn guess(file_path: ?[]const u8, content: []const u8) ?@This() {
|
||||
if (guess_first_line(content)) |ft| return ft;
|
||||
for (get_all_names()) |file_type_name| {
|
||||
const file_type = get(file_type_name) catch unreachable orelse unreachable;
|
||||
if (file_path) |fp| if (syntax.FileType.match_file_type(file_type.extensions orelse continue, fp))
|
||||
return file_type;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
fn guess_first_line(content: []const u8) ?@This() {
|
||||
const first_line = if (std.mem.indexOf(u8, content, "\n")) |pos| content[0..pos] else content;
|
||||
for (get_all_names()) |file_type_name| {
|
||||
const file_type = get(file_type_name) catch unreachable orelse unreachable;
|
||||
if (syntax.FileType.match_first_line(file_type.first_line_matches_prefix, file_type.first_line_matches_content, first_line))
|
||||
return file_type;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn create_syntax(file_type_config: @This(), allocator: std.mem.Allocator, query_cache: *syntax.QueryCache) !?*syntax {
|
||||
return syntax.create(
|
||||
syntax.FileType.get_by_name_static(file_type_config.parser orelse file_type_config.name) orelse return null,
|
||||
allocator,
|
||||
query_cache,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn create_syntax_guess_file_type(
|
||||
allocator: std.mem.Allocator,
|
||||
content: []const u8,
|
||||
file_path: ?[]const u8,
|
||||
query_cache: *syntax.QueryCache,
|
||||
) !?*syntax {
|
||||
const file_type = guess(file_path, content) orelse return error.NotFound;
|
||||
return create_syntax(file_type, allocator, query_cache);
|
||||
}
|
||||
|
||||
const syntax = @import("syntax");
|
||||
const std = @import("std");
|
||||
const root = @import("root");
|
|
@ -249,6 +249,7 @@
|
|||
"deinit_command": ["resume_undo_history"],
|
||||
"press": [
|
||||
["<Esc>", ["move_left_vim"], ["enter_mode", "normal"]],
|
||||
["<C-c>", ["move_left_vim"], ["enter_mode", "normal"]],
|
||||
["<Del>", "delete_forward"],
|
||||
["<BS>", "delete_backward"],
|
||||
["<CR>", "smart_insert_line"],
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const std = @import("std");
|
||||
const syntax = @import("syntax");
|
||||
const file_type_config = @import("file_type_config");
|
||||
const builtin = @import("builtin");
|
||||
const RGB = @import("color").RGB;
|
||||
|
||||
|
@ -16,7 +16,8 @@ pub fn list(allocator: std.mem.Allocator, writer: anytype, tty_config: std.io.tt
|
|||
var max_formatter_len: usize = 0;
|
||||
var max_extensions_len: usize = 0;
|
||||
|
||||
for (syntax.FileType.file_types) |file_type| {
|
||||
for (file_type_config.get_all_names()) |file_type_name| {
|
||||
const file_type = try file_type_config.get(file_type_name) orelse unreachable;
|
||||
max_language_len = @max(max_language_len, file_type.name.len);
|
||||
max_langserver_len = @max(max_langserver_len, args_string_length(file_type.language_server));
|
||||
max_formatter_len = @max(max_formatter_len, args_string_length(file_type.formatter));
|
||||
|
@ -31,10 +32,11 @@ pub fn list(allocator: std.mem.Allocator, writer: anytype, tty_config: std.io.tt
|
|||
try tty_config.setColor(writer, .reset);
|
||||
try writer.writeAll("\n");
|
||||
|
||||
for (syntax.FileType.file_types) |file_type| {
|
||||
for (file_type_config.get_all_names()) |file_type_name| {
|
||||
const file_type = try file_type_config.get(file_type_name) orelse unreachable;
|
||||
try writer.writeAll(" ");
|
||||
try setColorRgb(writer, file_type.color);
|
||||
try writer.writeAll(file_type.icon);
|
||||
try setColorRgb(writer, file_type.color orelse file_type_config.default.color);
|
||||
try writer.writeAll(file_type.icon orelse file_type_config.default.icon);
|
||||
try tty_config.setColor(writer, .reset);
|
||||
try writer.writeAll(" ");
|
||||
try write_string(writer, file_type.name, max_language_len + 1);
|
||||
|
|
96
src/main.zig
96
src/main.zig
|
@ -2,6 +2,7 @@ const std = @import("std");
|
|||
const tui = @import("tui");
|
||||
const cbor = @import("cbor");
|
||||
const thespian = @import("thespian");
|
||||
const color = @import("color");
|
||||
const flags = @import("flags");
|
||||
const builtin = @import("builtin");
|
||||
const bin_path = @import("bin_path");
|
||||
|
@ -148,11 +149,10 @@ pub fn main() anyerror!void {
|
|||
return list_languages.list(a, stdout.writer(), tty_config);
|
||||
}
|
||||
|
||||
if (builtin.os.tag != .windows)
|
||||
if (std.posix.getenv("JITDEBUG")) |_|
|
||||
thespian.install_debugger()
|
||||
else if (@hasDecl(renderer, "install_crash_handler"))
|
||||
renderer.install_crash_handler();
|
||||
if (builtin.os.tag != .windows and @hasDecl(renderer, "install_crash_handler")) {
|
||||
if (std.posix.getenv("JITDEBUG")) |_| renderer.jit_debugger_enabled = true;
|
||||
renderer.install_crash_handler();
|
||||
}
|
||||
|
||||
if (args.debug_wait) {
|
||||
std.debug.print("press return to start", .{});
|
||||
|
@ -445,13 +445,24 @@ pub fn exists_config(T: type) bool {
|
|||
return true;
|
||||
}
|
||||
|
||||
fn get_default(T: type) T {
|
||||
return switch (@typeInfo(T)) {
|
||||
.array => &.{},
|
||||
.pointer => |info| switch (info.size) {
|
||||
.slice => &.{},
|
||||
else => @compileError("unsupported config type " ++ @typeName(T)),
|
||||
},
|
||||
else => .{},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn read_config(T: type, allocator: std.mem.Allocator) struct { T, [][]const u8 } {
|
||||
config_mutex.lock();
|
||||
defer config_mutex.unlock();
|
||||
var bufs: [][]const u8 = &[_][]const u8{};
|
||||
const json_file_name = get_app_config_file_name(application_name, @typeName(T)) catch return .{ .{}, bufs };
|
||||
const json_file_name = get_app_config_file_name(application_name, @typeName(T)) catch return .{ get_default(T), bufs };
|
||||
const text_file_name = json_file_name[0 .. json_file_name.len - ".json".len];
|
||||
var conf: T = .{};
|
||||
var conf: T = get_default(T);
|
||||
if (!read_config_file(T, allocator, &conf, &bufs, text_file_name)) {
|
||||
_ = read_config_file(T, allocator, &conf, &bufs, json_file_name);
|
||||
}
|
||||
|
@ -476,12 +487,16 @@ fn read_config_file(T: type, allocator: std.mem.Allocator, conf: *T, bufs: *[][]
|
|||
fn read_text_config_file(T: type, allocator: std.mem.Allocator, conf: *T, bufs_: *[][]const u8, file_name: []const u8) !void {
|
||||
var file = try std.fs.openFileAbsolute(file_name, .{ .mode = .read_only });
|
||||
defer file.close();
|
||||
const text = try file.readToEndAlloc(allocator, 64 * 1024);
|
||||
defer allocator.free(text);
|
||||
const content = try file.readToEndAlloc(allocator, 64 * 1024);
|
||||
defer allocator.free(content);
|
||||
return parse_text_config_file(T, allocator, conf, bufs_, file_name, content);
|
||||
}
|
||||
|
||||
pub fn parse_text_config_file(T: type, allocator: std.mem.Allocator, conf: *T, bufs_: *[][]const u8, file_name: []const u8, content: []const u8) !void {
|
||||
var cbor_buf = std.ArrayList(u8).init(allocator);
|
||||
defer cbor_buf.deinit();
|
||||
const writer = cbor_buf.writer();
|
||||
var it = std.mem.splitScalar(u8, text, '\n');
|
||||
var it = std.mem.splitScalar(u8, content, '\n');
|
||||
var lineno: u32 = 0;
|
||||
while (it.next()) |line| {
|
||||
lineno += 1;
|
||||
|
@ -505,7 +520,7 @@ fn read_text_config_file(T: type, allocator: std.mem.Allocator, conf: *T, bufs_:
|
|||
var bufs = std.ArrayListUnmanaged([]const u8).fromOwnedSlice(bufs_.*);
|
||||
bufs.append(allocator, cb) catch @panic("OOM:read_text_config_file");
|
||||
bufs_.* = bufs.toOwnedSlice(allocator) catch @panic("OOM:read_text_config_file");
|
||||
return read_cbor_config(T, conf, file_name, cb);
|
||||
return read_cbor_config(T, allocator, conf, file_name, cb);
|
||||
}
|
||||
|
||||
fn read_json_config_file(T: type, allocator: std.mem.Allocator, conf: *T, bufs_: *[][]const u8, file_name: []const u8) !void {
|
||||
|
@ -520,11 +535,12 @@ fn read_json_config_file(T: type, allocator: std.mem.Allocator, conf: *T, bufs_:
|
|||
const cb = try cbor.fromJson(json, cbor_buf);
|
||||
var iter = cb;
|
||||
_ = try cbor.decodeMapHeader(&iter);
|
||||
return read_cbor_config(T, conf, file_name, iter);
|
||||
return read_cbor_config(T, allocator, conf, file_name, iter);
|
||||
}
|
||||
|
||||
fn read_cbor_config(
|
||||
T: type,
|
||||
allocator: std.mem.Allocator,
|
||||
conf: *T,
|
||||
file_name: []const u8,
|
||||
cb: []const u8,
|
||||
|
@ -554,12 +570,29 @@ fn read_cbor_config(
|
|||
}
|
||||
} else if (std.mem.eql(u8, field_name, field_info.name)) {
|
||||
known = true;
|
||||
var value: field_info.type = undefined;
|
||||
if (try cbor.matchValue(&iter, cbor.extract(&value))) {
|
||||
@field(conf, field_info.name) = value;
|
||||
} else {
|
||||
try cbor.skipValue(&iter);
|
||||
std.log.err("invalid value for key '{s}'", .{field_name});
|
||||
switch (field_info.type) {
|
||||
u24, ?u24 => {
|
||||
var value: []const u8 = undefined;
|
||||
if (try cbor.matchValue(&iter, cbor.extract(&value))) {
|
||||
const color_ = color.RGB.from_string(value);
|
||||
if (color_) |color__|
|
||||
@field(conf, field_info.name) = color__.to_u24()
|
||||
else
|
||||
std.log.err("invalid value for key '{s}'", .{field_name});
|
||||
} else {
|
||||
try cbor.skipValue(&iter);
|
||||
std.log.err("invalid value for key '{s}'", .{field_name});
|
||||
}
|
||||
},
|
||||
else => {
|
||||
var value: field_info.type = undefined;
|
||||
if (try cbor.matchValue(&iter, cbor.extractAlloc(&value, allocator))) {
|
||||
@field(conf, field_info.name) = value;
|
||||
} else {
|
||||
try cbor.skipValue(&iter);
|
||||
std.log.err("invalid value for key '{s}'", .{field_name});
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
if (!known) {
|
||||
|
@ -613,15 +646,36 @@ pub fn write_config_to_writer(comptime T: type, data: T, writer: anytype) @TypeO
|
|||
} else {
|
||||
try writer.print("{s} ", .{field_info.name});
|
||||
}
|
||||
var s = std.json.writeStream(writer, .{ .whitespace = .indent_4 });
|
||||
try s.write(@field(data, field_info.name));
|
||||
switch (field_info.type) {
|
||||
u24 => try write_color_value(@field(data, field_info.name), writer),
|
||||
?u24 => if (@field(data, field_info.name)) |value|
|
||||
try write_color_value(value, writer)
|
||||
else
|
||||
try writer.writeAll("null"),
|
||||
else => {
|
||||
var s = std.json.writeStream(writer, .{ .whitespace = .minified });
|
||||
try s.write(@field(data, field_info.name));
|
||||
},
|
||||
}
|
||||
try writer.print("\n", .{});
|
||||
}
|
||||
}
|
||||
|
||||
fn write_color_value(value: u24, writer: anytype) @TypeOf(writer).Error!void {
|
||||
var hex: [7]u8 = undefined;
|
||||
try writer.writeByte('"');
|
||||
try writer.writeAll(color.RGB.to_string(color.RGB.from_u24(value), &hex));
|
||||
try writer.writeByte('"');
|
||||
}
|
||||
|
||||
fn config_eql(comptime T: type, a: T, b: T) bool {
|
||||
switch (T) {
|
||||
[]const u8 => return std.mem.eql(u8, a, b),
|
||||
[]const []const u8 => {
|
||||
if (a.len != b.len) return false;
|
||||
for (a, 0..) |x, i| if (!config_eql([]const u8, x, b[i])) return false;
|
||||
return true;
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
switch (@typeInfo(T)) {
|
||||
|
@ -707,7 +761,7 @@ pub fn list_themes(allocator: std.mem.Allocator) ![]const []const u8 {
|
|||
return result.toOwnedSlice();
|
||||
}
|
||||
|
||||
pub fn get_config_dir() ![]const u8 {
|
||||
pub fn get_config_dir() ConfigDirError![]const u8 {
|
||||
return get_app_config_dir(application_name);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ const tp = @import("thespian");
|
|||
const cbor = @import("cbor");
|
||||
const log = @import("log");
|
||||
const tracy = @import("tracy");
|
||||
const FileType = @import("syntax").FileType;
|
||||
const file_type_config = @import("file_type_config");
|
||||
const root = @import("root");
|
||||
const Buffer = @import("Buffer");
|
||||
const builtin = @import("builtin");
|
||||
|
@ -137,7 +137,7 @@ pub fn delete_task(task: []const u8) (ProjectManagerError || ProjectError)!void
|
|||
return send(.{ "delete_task", project, task });
|
||||
}
|
||||
|
||||
pub fn did_open(file_path: []const u8, file_type: *const FileType, version: usize, text: []const u8, ephemeral: bool) (ProjectManagerError || ProjectError)!void {
|
||||
pub fn did_open(file_path: []const u8, file_type: file_type_config, version: usize, text: []const u8, ephemeral: bool) (ProjectManagerError || ProjectError)!void {
|
||||
if (ephemeral) return;
|
||||
const project = tp.env.get().str("project");
|
||||
if (project.len == 0)
|
||||
|
|
|
@ -11,10 +11,12 @@ const RGB = @import("color").RGB;
|
|||
|
||||
const Plane = @This();
|
||||
|
||||
const name_buf_len = 128;
|
||||
|
||||
window: vaxis.Window,
|
||||
row: i32 = 0,
|
||||
col: i32 = 0,
|
||||
name_buf: [128]u8,
|
||||
name_buf: [name_buf_len]u8,
|
||||
name_len: usize,
|
||||
cache: GraphemeCache = .{},
|
||||
style: vaxis.Cell.Style = .{},
|
||||
|
@ -27,7 +29,7 @@ pub const Options = struct {
|
|||
x: usize = 0,
|
||||
rows: usize = 0,
|
||||
cols: usize = 0,
|
||||
name: [*:0]const u8,
|
||||
name: []const u8,
|
||||
flags: option = .none,
|
||||
};
|
||||
|
||||
|
@ -44,13 +46,14 @@ pub fn init(nopts: *const Options, parent_: Plane) !Plane {
|
|||
.height = @as(u16, @intCast(nopts.rows)),
|
||||
.border = .{},
|
||||
};
|
||||
const len = @min(nopts.name.len, name_buf_len);
|
||||
var plane: Plane = .{
|
||||
.window = parent_.window.child(opts),
|
||||
.name_buf = undefined,
|
||||
.name_len = std.mem.span(nopts.name).len,
|
||||
.name_len = len,
|
||||
.scrolling = nopts.flags == .VSCROLL,
|
||||
};
|
||||
@memcpy(plane.name_buf[0..plane.name_len], nopts.name);
|
||||
@memcpy(plane.name_buf[0..len], nopts.name[0..len]);
|
||||
return plane;
|
||||
}
|
||||
|
||||
|
|
|
@ -57,6 +57,7 @@ pub const Error = error{
|
|||
InvalidPIntType,
|
||||
JsonIncompatibleType,
|
||||
NotAnObject,
|
||||
BadArrayAllocExtract,
|
||||
} || std.Thread.SpawnError;
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, handler_ctx: *anyopaque, no_alternate: bool, _: *const fn (ctx: *anyopaque) void) Error!Self {
|
||||
|
@ -135,15 +136,46 @@ pub fn install_crash_handler() void {
|
|||
std.posix.sigaction(std.posix.SIG.ILL, &act, null);
|
||||
}
|
||||
|
||||
pub var jit_debugger_enabled: bool = false;
|
||||
|
||||
fn handle_crash(sig: i32, info: *const std.posix.siginfo_t, ctx_ptr: ?*anyopaque) callconv(.c) noreturn {
|
||||
const debug = @import("std/debug.zig");
|
||||
debug.lockStdErr();
|
||||
|
||||
if (panic_in_progress())
|
||||
std.posix.abort();
|
||||
|
||||
in_panic.store(true, .release);
|
||||
const cleanup = panic_cleanup;
|
||||
panic_cleanup = null;
|
||||
|
||||
if (cleanup) |self| {
|
||||
self.vx.deinit(self.allocator, self.tty.anyWriter());
|
||||
self.tty.deinit();
|
||||
}
|
||||
@import("std/debug.zig").handleSegfaultPosix(sig, info, ctx_ptr);
|
||||
if (builtin.os.tag == .linux and jit_debugger_enabled) {
|
||||
handleSegfaultPosixNoAbort(sig, info, ctx_ptr);
|
||||
@import("thespian").sighdl_debugger(sig, @ptrCast(@constCast(info)), ctx_ptr);
|
||||
std.posix.abort();
|
||||
} else {
|
||||
debug.handleSegfaultPosix(sig, info, ctx_ptr);
|
||||
}
|
||||
unreachable;
|
||||
}
|
||||
|
||||
fn handleSegfaultPosixNoAbort(sig: i32, info: *const std.posix.siginfo_t, ctx_ptr: ?*anyopaque) void {
|
||||
const debug = @import("std/debug.zig");
|
||||
debug.resetSegfaultHandler();
|
||||
const addr = switch (builtin.os.tag) {
|
||||
.linux => @intFromPtr(info.fields.sigfault.addr),
|
||||
.freebsd, .macos => @intFromPtr(info.addr),
|
||||
.netbsd => @intFromPtr(info.info.reason.fault.addr),
|
||||
.openbsd => @intFromPtr(info.data.fault.addr),
|
||||
.solaris, .illumos => @intFromPtr(info.reason.fault.addr),
|
||||
else => unreachable,
|
||||
};
|
||||
const code = if (builtin.os.tag == .netbsd) info.info.code else info.code;
|
||||
debug.dumpSegfaultInfoPosix(sig, code, addr, ctx_ptr);
|
||||
}
|
||||
|
||||
pub fn run(self: *Self) Error!void {
|
||||
|
|
|
@ -1384,7 +1384,7 @@ pub fn attachSegfaultHandler() void {
|
|||
updateSegfaultHandler(&act);
|
||||
}
|
||||
|
||||
fn resetSegfaultHandler() void {
|
||||
pub fn resetSegfaultHandler() void {
|
||||
if (native_os == .windows) {
|
||||
if (windows_segfault_handle) |handle| {
|
||||
assert(windows.kernel32.RemoveVectoredExceptionHandler(handle) != 0);
|
||||
|
@ -1442,7 +1442,7 @@ pub fn handleSegfaultPosix(sig: i32, info: *const posix.siginfo_t, ctx_ptr: ?*an
|
|||
posix.abort();
|
||||
}
|
||||
|
||||
fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*anyopaque) void {
|
||||
pub fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*anyopaque) void {
|
||||
const stderr = io.getStdErr().writer();
|
||||
_ = switch (sig) {
|
||||
posix.SIG.SEGV => if (native_arch == .x86_64 and native_os == .linux and code == 128) // SI_KERNEL
|
||||
|
|
|
@ -35,6 +35,7 @@ pub const Error = error{
|
|||
InvalidPIntType,
|
||||
JsonIncompatibleType,
|
||||
NotAnObject,
|
||||
BadArrayAllocExtract,
|
||||
} || std.Thread.SpawnError;
|
||||
|
||||
pub const panic = messageBoxThenPanic(.{ .title = "Flow Panic" });
|
||||
|
|
|
@ -26,6 +26,7 @@ pub const Error = error{
|
|||
InvalidPIntType,
|
||||
JsonIncompatibleType,
|
||||
NotAnObject,
|
||||
BadArrayAllocExtract,
|
||||
};
|
||||
|
||||
pub const OutputHandler = fn (context: usize, parent: tp.pid_ref, arg0: []const u8, output: []const u8) void;
|
||||
|
|
|
@ -24,7 +24,8 @@ const CacheEntry = struct {
|
|||
query: ?*Query,
|
||||
query_arena: ?*std.heap.ArenaAllocator,
|
||||
query_type: QueryType,
|
||||
file_type: *const FileType,
|
||||
file_type_name: []const u8,
|
||||
lang_fn: FileType.LangFn,
|
||||
|
||||
fn destroy(self: *@This(), allocator: std.mem.Allocator) void {
|
||||
if (self.query_arena) |a| {
|
||||
|
@ -101,7 +102,7 @@ fn release_cache_entry_hash_map(allocator: std.mem.Allocator, hash_map: *std.Str
|
|||
hash_map.deinit(allocator);
|
||||
}
|
||||
|
||||
fn get_cache_entry(self: *Self, file_type: *const FileType, comptime query_type: QueryType) CacheError!*CacheEntry {
|
||||
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();
|
||||
|
||||
|
@ -119,7 +120,8 @@ fn get_cache_entry(self: *Self, file_type: *const FileType, comptime query_type:
|
|||
.query = null,
|
||||
.query_arena = null,
|
||||
.mutex = if (self.mutex) |_| .{} else null,
|
||||
.file_type = file_type,
|
||||
.lang_fn = file_type.lang_fn,
|
||||
.file_type_name = file_type.name,
|
||||
.query_type = query_type,
|
||||
};
|
||||
entry_.value_ptr.* = q;
|
||||
|
@ -133,8 +135,8 @@ fn get_cached_query(self: *Self, entry: *CacheEntry) Error!?*Query {
|
|||
defer if (entry.mutex) |*mtx| mtx.unlock();
|
||||
|
||||
return if (entry.query) |query| query else blk: {
|
||||
const lang = entry.file_type.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 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,
|
||||
|
@ -166,7 +168,7 @@ fn ReturnType(comptime query_type: QueryType) type {
|
|||
};
|
||||
}
|
||||
|
||||
pub fn get(self: *Self, file_type: *const FileType, comptime query_type: QueryType) Error!ReturnType(query_type) {
|
||||
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))) {
|
||||
|
|
|
@ -20,43 +20,45 @@ comment: []const u8,
|
|||
formatter: ?[]const []const u8,
|
||||
language_server: ?[]const []const u8,
|
||||
|
||||
pub fn get_by_name(name: []const u8) ?*const FileType {
|
||||
for (file_types) |*file_type|
|
||||
if (std.mem.eql(u8, file_type.name, name))
|
||||
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;
|
||||
}
|
||||
|
||||
pub fn guess(file_path: ?[]const u8, content: []const u8) ?*const FileType {
|
||||
if (guess_first_line(content)) |ft| return ft;
|
||||
for (file_types) |*file_type|
|
||||
if (file_path) |fp| if (match_file_type(file_type, fp))
|
||||
return file_type;
|
||||
return null;
|
||||
}
|
||||
|
||||
fn guess_first_line(content: []const u8) ?*const FileType {
|
||||
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 (file_types) |*file_type|
|
||||
for (static_file_types) |*file_type|
|
||||
if (file_type.first_line_matches) |match|
|
||||
if (match_first_line(match, first_line))
|
||||
if (match_first_line(match.prefix, match.content, first_line))
|
||||
return file_type;
|
||||
return null;
|
||||
}
|
||||
|
||||
fn match_first_line(match: FirstLineMatch, first_line: []const u8) bool {
|
||||
if (match.prefix) |prefix|
|
||||
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 (match_content) |content|
|
||||
if (std.mem.indexOf(u8, first_line, content)) |_| {} else return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
fn match_file_type(file_type: *const FileType, file_path: []const u8) bool {
|
||||
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 (file_type.extensions) |ext| {
|
||||
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..]))
|
||||
|
@ -85,14 +87,15 @@ fn ft_func_name(comptime lang: []const u8) []const u8 {
|
|||
return &func_name;
|
||||
}
|
||||
|
||||
const LangFn = *const fn () callconv(.C) ?*const treez.Language;
|
||||
pub const LangFn = *const fn () callconv(.C) ?*const treez.Language;
|
||||
|
||||
pub const FirstLineMatch = struct {
|
||||
prefix: ?[]const u8 = null,
|
||||
content: ?[]const u8 = null,
|
||||
};
|
||||
|
||||
pub const file_types = load_file_types(@import("file_types.zig"));
|
||||
const static_file_type_list = load_file_types(@import("file_types.zig"));
|
||||
const static_file_types = std.static_string_map.StaticStringMap(FileType).initComptime(static_file_type_list);
|
||||
|
||||
fn vec(comptime args: anytype) []const []const u8 {
|
||||
var cmd: []const []const u8 = &[_][]const u8{};
|
||||
|
@ -102,7 +105,9 @@ fn vec(comptime args: anytype) []const []const u8 {
|
|||
return cmd;
|
||||
}
|
||||
|
||||
fn load_file_types(comptime Namespace: type) []const FileType {
|
||||
const ListEntry = struct { []const u8, FileType };
|
||||
|
||||
fn load_file_types(comptime Namespace: type) []const ListEntry {
|
||||
comptime switch (@typeInfo(Namespace)) {
|
||||
.@"struct" => |info| {
|
||||
var count = 0;
|
||||
|
@ -110,12 +115,12 @@ fn load_file_types(comptime Namespace: type) []const FileType {
|
|||
// @compileLog(decl.name, @TypeOf(@field(Namespace, decl.name)));
|
||||
count += 1;
|
||||
}
|
||||
var construct_types: [count]FileType = undefined;
|
||||
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] = .{
|
||||
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,
|
||||
|
@ -126,7 +131,7 @@ fn load_file_types(comptime Namespace: type) []const FileType {
|
|||
.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;
|
||||
|
|
|
@ -21,14 +21,13 @@ pub const Node = treez.Node;
|
|||
|
||||
allocator: std.mem.Allocator,
|
||||
lang: *const Language,
|
||||
file_type: *const FileType,
|
||||
parser: *Parser,
|
||||
query: *Query,
|
||||
errors_query: *Query,
|
||||
injections: ?*Query,
|
||||
tree: ?*treez.Tree = null,
|
||||
|
||||
pub fn create(file_type: *const FileType, allocator: std.mem.Allocator, query_cache: *QueryCache) !*Self {
|
||||
pub fn create(file_type: FileType, allocator: std.mem.Allocator, query_cache: *QueryCache) !*Self {
|
||||
const query = try query_cache.get(file_type, .highlights);
|
||||
const errors_query = try query_cache.get(file_type, .errors);
|
||||
const injections = try query_cache.get(file_type, .injections);
|
||||
|
@ -36,7 +35,6 @@ pub fn create(file_type: *const FileType, allocator: std.mem.Allocator, query_ca
|
|||
self.* = .{
|
||||
.allocator = allocator,
|
||||
.lang = file_type.lang_fn() orelse std.debug.panic("tree-sitter parser function failed for language: {s}", .{file_type.name}),
|
||||
.file_type = file_type,
|
||||
.parser = try Parser.create(),
|
||||
.query = query,
|
||||
.errors_query = errors_query,
|
||||
|
@ -47,13 +45,13 @@ pub fn create(file_type: *const FileType, allocator: std.mem.Allocator, query_ca
|
|||
return self;
|
||||
}
|
||||
|
||||
pub fn create_file_type(allocator: std.mem.Allocator, lang_name: []const u8, query_cache: *QueryCache) !*Self {
|
||||
const file_type = FileType.get_by_name(lang_name) orelse return error.NotFound;
|
||||
pub fn static_create_file_type(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(allocator: std.mem.Allocator, content: []const u8, file_path: ?[]const u8, query_cache: *QueryCache) !*Self {
|
||||
const file_type = FileType.guess(file_path, content) orelse return error.NotFound;
|
||||
pub fn static_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);
|
||||
}
|
||||
|
||||
|
|
|
@ -280,6 +280,7 @@ pub const DeserializeError = error{
|
|||
JsonIncompatibleType,
|
||||
InvalidQueryCbor,
|
||||
NotAnObject,
|
||||
BadArrayAllocExtract,
|
||||
};
|
||||
|
||||
pub fn fromCbor(cb: []const u8, allocator: std.mem.Allocator) DeserializeError!struct { *TSQuery, *std.heap.ArenaAllocator } {
|
||||
|
|
|
@ -8,6 +8,7 @@ const ripgrep = @import("ripgrep");
|
|||
const tracy = @import("tracy");
|
||||
const text_manip = @import("text_manip");
|
||||
const syntax = @import("syntax");
|
||||
const file_type_config = @import("file_type_config");
|
||||
const project_manager = @import("project_manager");
|
||||
const root_mod = @import("root");
|
||||
|
||||
|
@ -330,6 +331,7 @@ pub const Editor = struct {
|
|||
utf8_sanitized: bool = false,
|
||||
} = .{},
|
||||
|
||||
file_type: ?file_type_config = null,
|
||||
syntax: ?*syntax = null,
|
||||
syntax_no_render: bool = false,
|
||||
syntax_report_timing: bool = false,
|
||||
|
@ -579,30 +581,30 @@ pub const Editor = struct {
|
|||
try new_buf.root.store(content.writer(std.heap.c_allocator), new_buf.file_eol_mode);
|
||||
}
|
||||
|
||||
const syn_file_type = blk: {
|
||||
self.file_type = blk: {
|
||||
const frame_ = tracy.initZone(@src(), .{ .name = "guess" });
|
||||
defer frame_.deinit();
|
||||
break :blk if (lang_override.len > 0)
|
||||
syntax.FileType.get_by_name(lang_override)
|
||||
try file_type_config.get(lang_override)
|
||||
else
|
||||
syntax.FileType.guess(self.file_path, content.items);
|
||||
file_type_config.guess_file_type(self.file_path, content.items);
|
||||
};
|
||||
|
||||
const syn = blk: {
|
||||
const frame_ = tracy.initZone(@src(), .{ .name = "create" });
|
||||
defer frame_.deinit();
|
||||
break :blk if (syn_file_type) |ft|
|
||||
syntax.create(ft, self.allocator, tui.query_cache()) catch null
|
||||
break :blk if (self.file_type) |ft|
|
||||
ft.create_syntax(self.allocator, tui.query_cache()) catch null
|
||||
else
|
||||
null;
|
||||
};
|
||||
|
||||
if (syn) |syn_| {
|
||||
if (self.file_type) |ft| {
|
||||
const frame_ = tracy.initZone(@src(), .{ .name = "did_open" });
|
||||
defer frame_.deinit();
|
||||
project_manager.did_open(
|
||||
file_path,
|
||||
syn_.file_type,
|
||||
ft,
|
||||
self.lsp_version,
|
||||
try content.toOwnedSlice(std.heap.c_allocator),
|
||||
new_buf.is_ephemeral(),
|
||||
|
@ -614,9 +616,9 @@ pub const Editor = struct {
|
|||
self.syntax_no_render = tp.env.get().is("no-syntax");
|
||||
self.syntax_report_timing = tp.env.get().is("syntax-report-timing");
|
||||
|
||||
const ftn = if (self.syntax) |syn| syn.file_type.name else "text";
|
||||
const fti = if (self.syntax) |syn| syn.file_type.icon else "🖹";
|
||||
const ftc = if (self.syntax) |syn| syn.file_type.color else 0x000000;
|
||||
const ftn = if (self.file_type) |ft| ft.name else file_type_config.default.name;
|
||||
const fti = if (self.file_type) |ft| ft.icon orelse file_type_config.default.icon else file_type_config.default.icon;
|
||||
const ftc = if (self.file_type) |ft| ft.color orelse file_type_config.default.color else file_type_config.default.color;
|
||||
if (self.buffer) |buffer| {
|
||||
buffer.file_type_name = ftn;
|
||||
buffer.file_type_icon = fti;
|
||||
|
@ -2946,30 +2948,46 @@ pub const Editor = struct {
|
|||
|
||||
for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel| {
|
||||
if (cursel.selection) |_| {
|
||||
// just delete selection
|
||||
root = self.delete_selection(root, cursel, b.allocator) catch continue;
|
||||
all_stop = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// detect indentation
|
||||
const first = find_first_non_ws(root, cursel.cursor.row, self.metrics);
|
||||
|
||||
// select char to the left
|
||||
with_selection_const(root, move_cursor_left, cursel, self.metrics) catch continue;
|
||||
|
||||
// if we don't have a selection after move_cursor_left there is nothing to delete
|
||||
if (cursel.selection) |*sel| {
|
||||
const egc_left, _, _ = sel.end.egc_at(root, self.metrics) catch {
|
||||
root = self.delete_selection(root, cursel, b.allocator) catch continue;
|
||||
all_stop = false;
|
||||
continue;
|
||||
};
|
||||
const egc_right, _, _ = sel.begin.egc_at(root, self.metrics) catch {
|
||||
root = self.delete_selection(root, cursel, b.allocator) catch continue;
|
||||
all_stop = false;
|
||||
continue;
|
||||
};
|
||||
if (first > sel.end.col) {
|
||||
// we are inside leading whitespace
|
||||
// select to next indentation boundary
|
||||
while (sel.end.col > 0 and sel.end.col % self.indent_size != 0)
|
||||
with_selection_const(root, move_cursor_left, cursel, self.metrics) catch break;
|
||||
} else {
|
||||
// char being deleted
|
||||
const egc_left, _, _ = sel.end.egc_at(root, self.metrics) catch {
|
||||
root = self.delete_selection(root, cursel, b.allocator) catch continue;
|
||||
all_stop = false;
|
||||
continue;
|
||||
};
|
||||
// char to the right of char being deleted
|
||||
const egc_right, _, _ = sel.begin.egc_at(root, self.metrics) catch {
|
||||
root = self.delete_selection(root, cursel, b.allocator) catch continue;
|
||||
all_stop = false;
|
||||
continue;
|
||||
};
|
||||
|
||||
for (Buffer.unicode.char_pairs) |pair| if (std.mem.eql(u8, egc_left, pair[0]) and std.mem.eql(u8, egc_right, pair[1])) {
|
||||
sel.begin.move_right(root, self.metrics) catch {};
|
||||
break;
|
||||
};
|
||||
// if left char is a smart pair left char, also delete smart pair right char
|
||||
for (Buffer.unicode.char_pairs) |pair| if (std.mem.eql(u8, egc_left, pair[0]) and std.mem.eql(u8, egc_right, pair[1])) {
|
||||
sel.begin.move_right(root, self.metrics) catch {};
|
||||
break;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
root = self.delete_selection(root, cursel, b.allocator) catch continue;
|
||||
all_stop = false;
|
||||
};
|
||||
|
@ -3592,7 +3610,7 @@ pub const Editor = struct {
|
|||
pub const toggle_prefix_meta: Meta = .{ .arguments = &.{.string} };
|
||||
|
||||
pub fn toggle_comment(self: *Self, _: Context) Result {
|
||||
const comment = if (self.syntax) |syn| syn.file_type.comment else "//";
|
||||
const comment = if (self.file_type) |file_type| file_type.comment else "#";
|
||||
return self.toggle_prefix(command.fmt(.{comment}));
|
||||
}
|
||||
pub const toggle_comment_meta: Meta = .{ .description = "Toggle comment" };
|
||||
|
@ -3634,7 +3652,7 @@ pub const Editor = struct {
|
|||
var cursel: CurSel = .{};
|
||||
cursel.cursor = cursor.*;
|
||||
const first = find_first_non_ws(root, cursel.cursor.row, self.metrics);
|
||||
if (first == 0) return error.Stop;
|
||||
if (first == 0) return root;
|
||||
const off = first % self.indent_size;
|
||||
const cols = if (off == 0) self.indent_size else off;
|
||||
const sel = cursel.enable_selection(root, self.metrics) catch return error.Stop;
|
||||
|
@ -4631,7 +4649,7 @@ pub const Editor = struct {
|
|||
var content = std.ArrayListUnmanaged(u8).empty;
|
||||
defer content.deinit(self.allocator);
|
||||
try root.store(content.writer(self.allocator), eol_mode);
|
||||
self.syntax = syntax.create_guess_file_type(self.allocator, content.items, self.file_path, tui.query_cache()) catch |e| switch (e) {
|
||||
self.syntax = file_type_config.create_syntax_guess_file_type(self.allocator, content.items, self.file_path, tui.query_cache()) catch |e| switch (e) {
|
||||
error.NotFound => null,
|
||||
else => return e,
|
||||
};
|
||||
|
@ -5521,7 +5539,7 @@ pub const Editor = struct {
|
|||
pub const select_meta: Meta = .{ .arguments = &.{ .integer, .integer, .integer, .integer } };
|
||||
|
||||
fn get_formatter(self: *Self) ?[]const []const u8 {
|
||||
if (self.syntax) |syn| if (syn.file_type.formatter) |fmtr| if (fmtr.len > 0) return fmtr;
|
||||
if (self.file_type) |file_type| if (file_type.formatter) |fmtr| if (fmtr.len > 0) return fmtr;
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -5727,11 +5745,11 @@ pub const Editor = struct {
|
|||
saved.cursor = sel.end;
|
||||
break :ret sel;
|
||||
};
|
||||
var result = std.ArrayListUnmanaged(u8).empty;
|
||||
defer result.deinit(self.allocator);
|
||||
var result = std.ArrayList(u8).init(self.allocator);
|
||||
defer result.deinit();
|
||||
const writer: struct {
|
||||
self_: *Self,
|
||||
result: *std.ArrayListUnmanaged(u8),
|
||||
result: *std.ArrayList(u8),
|
||||
allocator: std.mem.Allocator,
|
||||
|
||||
const Error = @typeInfo(@typeInfo(@TypeOf(Buffer.unicode.LetterCasing.toUpperStr)).@"fn".return_type.?).error_union.error_set;
|
||||
|
@ -5742,7 +5760,7 @@ pub const Editor = struct {
|
|||
else
|
||||
try letter_casing.toLowerStr(writer.self_.allocator, bytes);
|
||||
defer writer.self_.allocator.free(flipped);
|
||||
return writer.result.appendSlice(writer.allocator, flipped);
|
||||
return writer.result.appendSlice(flipped);
|
||||
}
|
||||
fn map_error(e: anyerror, _: ?*std.builtin.StackTrace) Error {
|
||||
return @errorCast(e);
|
||||
|
@ -5817,29 +5835,38 @@ pub const Editor = struct {
|
|||
self.syntax_refresh_full = true;
|
||||
self.syntax_incremental_reparse = false;
|
||||
|
||||
self.syntax = syntax: {
|
||||
const file_type_config_ = try file_type_config.get(file_type);
|
||||
self.file_type = file_type_config_;
|
||||
|
||||
self.syntax = blk: {
|
||||
break :blk if (self.file_type) |ft|
|
||||
ft.create_syntax(self.allocator, tui.query_cache()) catch null
|
||||
else
|
||||
null;
|
||||
};
|
||||
|
||||
if (self.file_type) |ft| {
|
||||
var content = std.ArrayListUnmanaged(u8).empty;
|
||||
defer content.deinit(std.heap.c_allocator);
|
||||
const root = try self.buf_root();
|
||||
try root.store(content.writer(std.heap.c_allocator), try self.buf_eol_mode());
|
||||
const syn = syntax.create_file_type(self.allocator, file_type, tui.query_cache()) catch null;
|
||||
if (syn) |syn_| if (self.file_path) |file_path|
|
||||
|
||||
if (self.file_path) |file_path|
|
||||
project_manager.did_open(
|
||||
file_path,
|
||||
syn_.file_type,
|
||||
ft,
|
||||
self.lsp_version,
|
||||
try content.toOwnedSlice(std.heap.c_allocator),
|
||||
if (self.buffer) |p| p.is_ephemeral() else true,
|
||||
) catch |e|
|
||||
self.logger.print("project_manager.did_open failed: {any}", .{e});
|
||||
break :syntax syn;
|
||||
};
|
||||
}
|
||||
self.syntax_no_render = tp.env.get().is("no-syntax");
|
||||
self.syntax_report_timing = tp.env.get().is("syntax-report-timing");
|
||||
|
||||
const ftn = if (self.syntax) |syn| syn.file_type.name else "text";
|
||||
const fti = if (self.syntax) |syn| syn.file_type.icon else "🖹";
|
||||
const ftc = if (self.syntax) |syn| syn.file_type.color else 0x000000;
|
||||
const ftn = if (self.file_type) |ft| ft.name else "text";
|
||||
const fti = if (self.file_type) |ft| ft.icon orelse "🖹" else "🖹";
|
||||
const ftc = if (self.file_type) |ft| ft.color orelse 0x000000 else 0x000000;
|
||||
const file_exists = if (self.buffer) |b| b.file_exists else false;
|
||||
try self.send_editor_open(self.file_path orelse "", file_exists, ftn, fti, ftc);
|
||||
self.logger.print("file type {s}", .{file_type});
|
||||
|
|
|
@ -8,6 +8,8 @@ const location_history = @import("location_history");
|
|||
const project_manager = @import("project_manager");
|
||||
const log = @import("log");
|
||||
const shell = @import("shell");
|
||||
const syntax = @import("syntax");
|
||||
const file_type_config = @import("file_type_config");
|
||||
const builtin = @import("builtin");
|
||||
const build_options = @import("build_options");
|
||||
|
||||
|
@ -492,6 +494,46 @@ const cmds = struct {
|
|||
}
|
||||
pub const open_home_style_config_meta: Meta = .{ .description = "Edit home screen" };
|
||||
|
||||
pub fn change_file_type(_: *Self, _: Ctx) Result {
|
||||
return tui.open_overlay(
|
||||
@import("mode/overlay/file_type_palette.zig").Variant("set_file_type", "Select file type", false).Type,
|
||||
);
|
||||
}
|
||||
pub const change_file_type_meta: Meta = .{ .description = "Change file type" };
|
||||
|
||||
pub fn open_file_type_config(self: *Self, ctx: Ctx) Result {
|
||||
var file_type_name: []const u8 = undefined;
|
||||
if (!(ctx.args.match(.{tp.extract(&file_type_name)}) catch false))
|
||||
return tui.open_overlay(
|
||||
@import("mode/overlay/file_type_palette.zig").Variant("open_file_type_config", "Edit file type", true).Type,
|
||||
);
|
||||
|
||||
const file_name = try file_type_config.get_config_file_path(self.allocator, file_type_name);
|
||||
defer self.allocator.free(file_name);
|
||||
|
||||
const file: ?std.fs.File = std.fs.openFileAbsolute(file_name, .{ .mode = .read_only }) catch null;
|
||||
if (file) |f| {
|
||||
f.close();
|
||||
return tp.self_pid().send(.{ "cmd", "navigate", .{ .file = file_name } });
|
||||
}
|
||||
|
||||
const content = try file_type_config.get_default(self.allocator, file_type_name);
|
||||
defer self.allocator.free(content);
|
||||
|
||||
tui.reset_drag_context();
|
||||
try self.create_editor();
|
||||
try command.executeName("open_scratch_buffer", command.fmt(.{
|
||||
file_name,
|
||||
content,
|
||||
"conf",
|
||||
}));
|
||||
if (self.get_active_buffer()) |buffer| buffer.mark_not_ephemeral();
|
||||
}
|
||||
pub const open_file_type_config_meta: Meta = .{
|
||||
.arguments = &.{.string},
|
||||
.description = "Edit file type configuration",
|
||||
};
|
||||
|
||||
pub fn create_scratch_buffer(self: *Self, ctx: Ctx) Result {
|
||||
const args = try ctx.args.clone(self.allocator);
|
||||
defer self.allocator.free(args.buf);
|
||||
|
|
|
@ -2,129 +2,135 @@ const std = @import("std");
|
|||
const cbor = @import("cbor");
|
||||
const tp = @import("thespian");
|
||||
const syntax = @import("syntax");
|
||||
const file_type_config = @import("file_type_config");
|
||||
|
||||
const Widget = @import("../../Widget.zig");
|
||||
const tui = @import("../../tui.zig");
|
||||
|
||||
pub const Type = @import("palette.zig").Create(@This());
|
||||
pub fn Variant(comptime command: []const u8, comptime label_: []const u8, allow_previous: bool) type {
|
||||
return struct {
|
||||
pub const Type = @import("palette.zig").Create(@This());
|
||||
|
||||
pub const label = "Select file type";
|
||||
pub const name = " file type";
|
||||
pub const description = "file type";
|
||||
pub const label = label_;
|
||||
pub const name = " file type";
|
||||
pub const description = "file type";
|
||||
|
||||
pub const Entry = struct {
|
||||
label: []const u8,
|
||||
name: []const u8,
|
||||
icon: []const u8,
|
||||
color: u24,
|
||||
};
|
||||
pub const Entry = struct {
|
||||
label: []const u8,
|
||||
name: []const u8,
|
||||
icon: []const u8,
|
||||
color: u24,
|
||||
};
|
||||
|
||||
pub const Match = struct {
|
||||
name: []const u8,
|
||||
score: i32,
|
||||
matches: []const usize,
|
||||
};
|
||||
pub const Match = struct {
|
||||
name: []const u8,
|
||||
score: i32,
|
||||
matches: []const usize,
|
||||
};
|
||||
|
||||
var previous_file_type: ?[]const u8 = null;
|
||||
var previous_file_type: ?[]const u8 = null;
|
||||
|
||||
pub fn load_entries(palette: *Type) !usize {
|
||||
var longest_hint: usize = 0;
|
||||
var idx: usize = 0;
|
||||
previous_file_type = blk: {
|
||||
if (tui.get_active_editor()) |editor|
|
||||
if (editor.syntax) |editor_syntax|
|
||||
break :blk editor_syntax.file_type.name;
|
||||
break :blk null;
|
||||
pub fn load_entries(palette: *Type) !usize {
|
||||
var longest_hint: usize = 0;
|
||||
var idx: usize = 0;
|
||||
previous_file_type = blk: {
|
||||
if (tui.get_active_editor()) |editor|
|
||||
if (editor.file_type) |editor_file_type|
|
||||
break :blk editor_file_type.name;
|
||||
break :blk null;
|
||||
};
|
||||
|
||||
for (file_type_config.get_all_names()) |file_type_name| {
|
||||
const file_type = try file_type_config.get(file_type_name) orelse unreachable;
|
||||
idx += 1;
|
||||
(try palette.entries.addOne()).* = .{
|
||||
.label = file_type.description orelse file_type_config.default.description,
|
||||
.name = file_type.name,
|
||||
.icon = file_type.icon orelse file_type_config.default.icon,
|
||||
.color = file_type.color orelse file_type_config.default.color,
|
||||
};
|
||||
if (previous_file_type) |previous_name| if (std.mem.eql(u8, file_type.name, previous_name)) {
|
||||
palette.initial_selected = idx;
|
||||
};
|
||||
longest_hint = @max(longest_hint, file_type.name.len);
|
||||
}
|
||||
return longest_hint;
|
||||
}
|
||||
|
||||
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 cbor.writeValue(writer, entry.label);
|
||||
try cbor.writeValue(writer, entry.icon);
|
||||
try cbor.writeValue(writer, entry.color);
|
||||
try cbor.writeValue(writer, entry.name);
|
||||
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 {
|
||||
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 description_: []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.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 {};
|
||||
}
|
||||
button.plane.set_style(style_label);
|
||||
_ = button.plane.print("{s} ", .{description_}) catch {};
|
||||
|
||||
var name_: []const u8 = undefined;
|
||||
if (!(cbor.matchString(&iter, &name_) catch false))
|
||||
name_ = "";
|
||||
button.plane.set_style(style_hint);
|
||||
_ = button.plane.print_aligned_right(0, "{s} ", .{name_}) 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;
|
||||
}
|
||||
|
||||
fn select(menu: **Type.MenuState, button: *Type.ButtonState) void {
|
||||
var description_: []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.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_))
|
||||
return;
|
||||
tp.self_pid().send(.{ "cmd", "exit_overlay_mode" }) catch |e| menu.*.opts.ctx.logger.err("file_type_palette", e);
|
||||
tp.self_pid().send(.{ "cmd", command, .{name_} }) catch |e| menu.*.opts.ctx.logger.err("file_type_palette", e);
|
||||
}
|
||||
};
|
||||
|
||||
for (syntax.FileType.file_types) |file_type| {
|
||||
idx += 1;
|
||||
(try palette.entries.addOne()).* = .{
|
||||
.label = file_type.description,
|
||||
.name = file_type.name,
|
||||
.icon = file_type.icon,
|
||||
.color = file_type.color,
|
||||
};
|
||||
if (previous_file_type) |file_type_name| if (std.mem.eql(u8, file_type.name, file_type_name)) {
|
||||
palette.initial_selected = idx;
|
||||
};
|
||||
longest_hint = @max(longest_hint, file_type.name.len);
|
||||
}
|
||||
return longest_hint;
|
||||
}
|
||||
|
||||
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 cbor.writeValue(writer, entry.label);
|
||||
try cbor.writeValue(writer, entry.icon);
|
||||
try cbor.writeValue(writer, entry.color);
|
||||
try cbor.writeValue(writer, entry.name);
|
||||
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 {
|
||||
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 description_: []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.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 {};
|
||||
}
|
||||
button.plane.set_style(style_label);
|
||||
_ = button.plane.print("{s} ", .{description_}) catch {};
|
||||
|
||||
var name_: []const u8 = undefined;
|
||||
if (!(cbor.matchString(&iter, &name_) catch false))
|
||||
name_ = "";
|
||||
button.plane.set_style(style_hint);
|
||||
_ = button.plane.print_aligned_right(0, "{s} ", .{name_}) 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;
|
||||
}
|
||||
|
||||
fn select(menu: **Type.MenuState, button: *Type.ButtonState) void {
|
||||
var description_: []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.matchInt(u24, &iter, &color) catch false)) return;
|
||||
if (!(cbor.matchString(&iter, &name_) catch false)) return;
|
||||
if (previous_file_type) |prev| if (std.mem.eql(u8, prev, name_))
|
||||
return;
|
||||
tp.self_pid().send(.{ "cmd", "exit_overlay_mode" }) catch |e| menu.*.opts.ctx.logger.err("file_type_palette", e);
|
||||
tp.self_pid().send(.{ "cmd", "set_file_type", .{name_} }) catch |e| menu.*.opts.ctx.logger.err("file_type_palette", e);
|
||||
}
|
||||
|
|
|
@ -105,8 +105,8 @@ fn init(allocator: Allocator) InitError!*Self {
|
|||
var conf, const conf_bufs = root.read_config(@import("config"), allocator);
|
||||
defer root.free_config(allocator, conf_bufs);
|
||||
|
||||
if (conf.start_debugger_on_crash)
|
||||
tp.install_debugger();
|
||||
if (@hasDecl(renderer, "install_crash_handler") and conf.start_debugger_on_crash)
|
||||
renderer.jit_debugger_enabled = true;
|
||||
|
||||
const theme_, const parsed_theme = get_theme_by_name(allocator, conf.theme) orelse get_theme_by_name(allocator, "dark_modern") orelse return error.UnknownTheme;
|
||||
conf.theme = theme_.name;
|
||||
|
@ -917,11 +917,6 @@ const cmds = struct {
|
|||
}
|
||||
pub const change_theme_meta: Meta = .{ .description = "Change color theme" };
|
||||
|
||||
pub fn change_file_type(self: *Self, _: Ctx) Result {
|
||||
return self.enter_overlay_mode(@import("mode/overlay/file_type_palette.zig").Type);
|
||||
}
|
||||
pub const change_file_type_meta: Meta = .{ .description = "Change file type" };
|
||||
|
||||
pub fn change_fontface(self: *Self, _: Ctx) Result {
|
||||
if (build_options.gui)
|
||||
self.rdr_.get_fontfaces();
|
||||
|
@ -1117,6 +1112,10 @@ pub fn mini_mode() ?*MiniMode {
|
|||
return if (current().mini_mode_) |*p| p else null;
|
||||
}
|
||||
|
||||
pub fn open_overlay(mode: type) command.Result {
|
||||
return current().enter_overlay_mode(mode);
|
||||
}
|
||||
|
||||
pub fn query_cache() *syntax.QueryCache {
|
||||
return current().query_cache_;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue