diff --git a/README.md b/README.md index cf7be9b..03ba453 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,14 @@ You may install it on another system by simply copying the binary. scp zig-out/bin/flow root@otherhost:/usr/local/bin ``` +Configuration is mostly dynamically maintained with various commands in the UI. +It is stored under the standard user configuration path. Usually `~/.config/flow` +on Linux. %APPDATA%\Roaming\flow on Windows. Somewhere magical on MacOS + +Logs, traces and per-project most recently used file lists are stored in the +standard user application state directory. Usually `~/.local/state/flow` on +Linux and %APPDATA%\Roaming\flow on Windows. + Files to load may be specifed on the command line: ```shell @@ -125,26 +133,9 @@ Show supported language names with `--list-languages`. See `flow --help` for the full list of command line options. -# Configuration - -Configuration is mostly dynamically maintained with various commands in the UI. -It is stored under the standard user configuration path. Usually `~/.config/flow` -on Linux. %APPDATA%\Roaming\flow on Windows. Somewhere magical on MacOS. - -There are commands to open the various configuration files, so you don't have to -manually find them. Look for commands starting with `Edit` in the command palette. - -File types may be configured with the `Edit file type configuration` command. You -can also create a new file type by adding a new `.conf` file to the `file_type` -directory. Have a look at an existing file type to see what options are available. - -Logs, traces and per-project most recently used file lists are stored in the -standard user application state directory. Usually `~/.local/state/flow` on -Linux and %APPDATA%\Roaming\flow on Windows. - # Key bindings and commands -Press `F4` to switch the current keybinding mode. (flow, vim, emacs, etc.) +Press `F2` to switch the current keybinding mode. (flow, vim, emacs, etc.) Press `ctrl+shift+p` or `alt+x` to show the command palette. Press `ctrl+F2` to see a full list of all current keybindings and commands. diff --git a/build.zig b/build.zig index 313e84b..c508451 100644 --- a/build.zig +++ b/build.zig @@ -317,13 +317,6 @@ pub fn build_exe( }, }); - const file_type_config_mod = b.createModule(.{ - .root_source_file = b.path("src/file_type_config.zig"), - .imports = &.{ - .{ .name = "syntax", .module = syntax_mod }, - }, - }); - const log_mod = b.createModule(.{ .root_source_file = b.path("src/log.zig"), .imports = &.{ @@ -505,7 +498,7 @@ pub fn build_exe( .{ .name = "thespian", .module = thespian_mod }, .{ .name = "Buffer", .module = Buffer_mod }, .{ .name = "tracy", .module = tracy_mod }, - .{ .name = "file_type_config", .module = file_type_config_mod }, + .{ .name = "syntax", .module = syntax_mod }, .{ .name = "dizzy", .module = dizzy_dep.module("dizzy") }, .{ .name = "fuzzig", .module = fuzzig_dep.module("fuzzig") }, .{ .name = "git", .module = git_mod }, @@ -538,7 +531,6 @@ pub fn build_exe( .{ .name = "cbor", .module = cbor_mod }, .{ .name = "config", .module = config_mod }, .{ .name = "gui_config", .module = gui_config_mod }, - .{ .name = "file_type_config", .module = file_type_config_mod }, .{ .name = "log", .module = log_mod }, .{ .name = "command", .module = command_mod }, .{ .name = "EventHandler", .module = EventHandler_mod }, @@ -591,7 +583,6 @@ pub fn build_exe( exe.root_module.addImport("renderer", renderer_mod); exe.root_module.addImport("input", input_mod); exe.root_module.addImport("syntax", syntax_mod); - exe.root_module.addImport("file_type_config", file_type_config_mod); exe.root_module.addImport("color", color_mod); exe.root_module.addImport("bin_path", bin_path_mod); exe.root_module.addImport("version", b.createModule(.{ .root_source_file = version_file })); @@ -636,7 +627,6 @@ pub fn build_exe( check_exe.root_module.addImport("renderer", renderer_mod); check_exe.root_module.addImport("input", input_mod); check_exe.root_module.addImport("syntax", syntax_mod); - check_exe.root_module.addImport("file_type_config", file_type_config_mod); check_exe.root_module.addImport("color", color_mod); check_exe.root_module.addImport("bin_path", bin_path_mod); check_exe.root_module.addImport("version", b.createModule(.{ .root_source_file = version_file })); diff --git a/build.zig.version b/build.zig.version index ca633d3..aa1059e 100644 --- a/build.zig.version +++ b/build.zig.version @@ -1 +1 @@ -0.15.0-dev.936+fc2c1883b +0.15.0-dev.877+0adcfd60f diff --git a/build.zig.zon b/build.zig.zon index a2e2509..0a6e1a8 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -15,8 +15,8 @@ .hash = "dizzy-1.0.0-AAAAAM1wAAAiDbx_6RwcVEOBk8p2XOu8t9WPNc3K7kBK", }, .thespian = .{ - .url = "https://github.com/neurocyte/thespian/archive/9af61100d08d79ec52e98fa34ea36611dcc1c361.tar.gz", - .hash = "thespian-0.0.1-owFOjlgaBgAwqxQerLVXpy7blPPZjTqdfH9MLf9_v7dt", + .url = "https://github.com/neurocyte/thespian/archive/829a8d33e92988a51a8c51d204ec766a28c7903d.tar.gz", + .hash = "thespian-0.0.1-owFOjs0TBgAAed7EtHDPtpB7NBn-riNjb7Rkc7a_Voow", }, .themes = .{ .url = "https://github.com/neurocyte/flow-themes/releases/download/master-952f9f630ea9544088fd30293666ee0650b7a690/flow-themes.tar.gz", @@ -27,12 +27,12 @@ .hash = "fuzzig-0.1.1-AAAAALNIAQBmbHr-MPalGuR393Vem2pTQXI7_LXeNJgX", }, .vaxis = .{ - .url = "https://github.com/neurocyte/libvaxis/archive/2a4137dadbe560b13b712fd3aa8a1c313fdd8c6e.tar.gz", - .hash = "vaxis-0.1.0-BWNV_KMOCQAe8oPD6cCn62Rg7oIVgF8FEWLpcAi7xDZQ", + .url = "https://github.com/neurocyte/libvaxis/archive/64a29f4f91292bc79dc9afb9a254cbdfb35e29a6.tar.gz", + .hash = "vaxis-0.1.0-BWNV_KEOCQCzedUR1prWhPUcgsRmw8f-r5JBc3s4DLJf", }, .zeit = .{ - .url = "https://github.com/rockorager/zeit/archive/991f38266f86535e68431675e8feb84efa1f011b.tar.gz", - .hash = "zeit-0.6.0-5I6bk0t7AgCPM_cY1DoqJB2pnmG7MMtpdO5IxNpryJDy", + .url = "https://github.com/rockorager/zeit/archive/8fd203f85f597f16e0a525c1f1ca1e0bffded809.tar.gz", + .hash = "zeit-0.0.0-AAAAACVbAgAiIzg1rccZU1qOfO_dKQKme7-37xmEQcqc", }, .win32 = .{ .url = "https://github.com/marlersoft/zigwin32/archive/e8739b32a33ce48a3286aba31918b26a9dfc6ef0.tar.gz", diff --git a/help.md b/help.md index 5db2e7b..676b5b3 100644 --- a/help.md +++ b/help.md @@ -157,17 +157,19 @@ Configuration is stored in the standard location The default configuration will be written the first time Flow Control is started and looks similar to this: ``` -frame_rate 60 -theme "default" -input_mode "flow" -gutter_line_numbers true -gutter_line_numbers_relative false -enable_terminal_cursor false -highlight_current_line true -highlight_current_line_gutter true -show_whitespace false -animation_min_lag 0 -animation_max_lag 150 +{ + "frame_rate": 60, + "theme": "default", + "input_mode": "flow", + "gutter_line_numbers": true, + "gutter_line_numbers_relative": false, + "enable_terminal_cursor": false, + "highlight_current_line": true, + "highlight_current_line_gutter": true, + "show_whitespace": false, + "animation_min_lag": 0, + "animation_max_lag": 150 +} ``` Most of these options are fairly self explanitory. @@ -181,7 +183,3 @@ of frames rendered. `animation_max_lag` controls the maximum amount of time allowed for rendering scrolling animations. Set to 0 to disable scrolling animation altogether. - -File types may be configured with the `Edit file type configuration` command. You -can also create a new file type by adding a new `.conf` file to the `file_type` -directory. Have a look at an existing file type to see what options are available. diff --git a/src/Project.zig b/src/Project.zig index 19e4399..489c328 100644 --- a/src/Project.zig +++ b/src/Project.zig @@ -230,7 +230,6 @@ 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(); diff --git a/src/color.zig b/src/color.zig index 47be2ec..c3bb124 100644 --- a/src/color.zig +++ b/src/color.zig @@ -23,51 +23,10 @@ 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(); diff --git a/src/file_type_config.zig b/src/file_type_config.zig deleted file mode 100644 index 008bb9d..0000000 --- a/src/file_type_config.zig +++ /dev/null @@ -1,202 +0,0 @@ -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"); diff --git a/src/keybind/builtin/vim.json b/src/keybind/builtin/vim.json index 98e9b90..19f1685 100644 --- a/src/keybind/builtin/vim.json +++ b/src/keybind/builtin/vim.json @@ -249,7 +249,6 @@ "deinit_command": ["resume_undo_history"], "press": [ ["", ["move_left_vim"], ["enter_mode", "normal"]], - ["", ["move_left_vim"], ["enter_mode", "normal"]], ["", "delete_forward"], ["", "delete_backward"], ["", "smart_insert_line"], diff --git a/src/list_languages.zig b/src/list_languages.zig index 6efbd10..31de403 100644 --- a/src/list_languages.zig +++ b/src/list_languages.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const file_type_config = @import("file_type_config"); +const syntax = @import("syntax"); const builtin = @import("builtin"); const RGB = @import("color").RGB; @@ -16,8 +16,7 @@ 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 (file_type_config.get_all_names()) |file_type_name| { - const file_type = try file_type_config.get(file_type_name) orelse unreachable; + for (syntax.FileType.file_types) |file_type| { 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)); @@ -32,11 +31,10 @@ 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 (file_type_config.get_all_names()) |file_type_name| { - const file_type = try file_type_config.get(file_type_name) orelse unreachable; + for (syntax.FileType.file_types) |file_type| { try writer.writeAll(" "); - 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 setColorRgb(writer, file_type.color); + try writer.writeAll(file_type.icon); try tty_config.setColor(writer, .reset); try writer.writeAll(" "); try write_string(writer, file_type.name, max_language_len + 1); diff --git a/src/main.zig b/src/main.zig index 01641d9..0d6c119 100644 --- a/src/main.zig +++ b/src/main.zig @@ -2,7 +2,6 @@ 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"); @@ -149,10 +148,11 @@ pub fn main() anyerror!void { return list_languages.list(a, stdout.writer(), tty_config); } - 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 (builtin.os.tag != .windows) + if (std.posix.getenv("JITDEBUG")) |_| + thespian.install_debugger() + else if (@hasDecl(renderer, "install_crash_handler")) + renderer.install_crash_handler(); if (args.debug_wait) { std.debug.print("press return to start", .{}); @@ -445,24 +445,13 @@ 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 .{ get_default(T), bufs }; + const json_file_name = get_app_config_file_name(application_name, @typeName(T)) catch return .{ .{}, bufs }; const text_file_name = json_file_name[0 .. json_file_name.len - ".json".len]; - var conf: T = get_default(T); + var conf: T = .{}; if (!read_config_file(T, allocator, &conf, &bufs, text_file_name)) { _ = read_config_file(T, allocator, &conf, &bufs, json_file_name); } @@ -487,16 +476,12 @@ 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 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 { + const text = try file.readToEndAlloc(allocator, 64 * 1024); + defer allocator.free(text); var cbor_buf = std.ArrayList(u8).init(allocator); defer cbor_buf.deinit(); const writer = cbor_buf.writer(); - var it = std.mem.splitScalar(u8, content, '\n'); + var it = std.mem.splitScalar(u8, text, '\n'); var lineno: u32 = 0; while (it.next()) |line| { lineno += 1; @@ -520,7 +505,7 @@ pub fn parse_text_config_file(T: type, allocator: std.mem.Allocator, conf: *T, b 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, allocator, conf, file_name, cb); + return read_cbor_config(T, 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 { @@ -535,12 +520,11 @@ 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, allocator, conf, file_name, iter); + return read_cbor_config(T, conf, file_name, iter); } fn read_cbor_config( T: type, - allocator: std.mem.Allocator, conf: *T, file_name: []const u8, cb: []const u8, @@ -570,29 +554,12 @@ fn read_cbor_config( } } else if (std.mem.eql(u8, field_name, field_info.name)) { known = true; - 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}); - } - }, + 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}); } }; if (!known) { @@ -646,36 +613,15 @@ pub fn write_config_to_writer(comptime T: type, data: T, writer: anytype) @TypeO } else { try writer.print("{s} ", .{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)); - }, - } + var s = std.json.writeStream(writer, .{ .whitespace = .indent_4 }); + 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)) { @@ -761,7 +707,7 @@ pub fn list_themes(allocator: std.mem.Allocator) ![]const []const u8 { return result.toOwnedSlice(); } -pub fn get_config_dir() ConfigDirError![]const u8 { +pub fn get_config_dir() ![]const u8 { return get_app_config_dir(application_name); } diff --git a/src/project_manager.zig b/src/project_manager.zig index 78dde81..a9c5466 100644 --- a/src/project_manager.zig +++ b/src/project_manager.zig @@ -3,7 +3,7 @@ const tp = @import("thespian"); const cbor = @import("cbor"); const log = @import("log"); const tracy = @import("tracy"); -const file_type_config = @import("file_type_config"); +const FileType = @import("syntax").FileType; 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: file_type_config, version: usize, text: []const u8, ephemeral: bool) (ProjectManagerError || ProjectError)!void { +pub fn did_open(file_path: []const u8, file_type: *const FileType, version: usize, text: []const u8, ephemeral: bool) (ProjectManagerError || ProjectError)!void { if (ephemeral) return; const project = tp.env.get().str("project"); if (project.len == 0) diff --git a/src/renderer/vaxis/Plane.zig b/src/renderer/vaxis/Plane.zig index 34f68a8..80df067 100644 --- a/src/renderer/vaxis/Plane.zig +++ b/src/renderer/vaxis/Plane.zig @@ -11,12 +11,10 @@ const RGB = @import("color").RGB; const Plane = @This(); -const name_buf_len = 128; - window: vaxis.Window, row: i32 = 0, col: i32 = 0, -name_buf: [name_buf_len]u8, +name_buf: [128]u8, name_len: usize, cache: GraphemeCache = .{}, style: vaxis.Cell.Style = .{}, @@ -29,7 +27,7 @@ pub const Options = struct { x: usize = 0, rows: usize = 0, cols: usize = 0, - name: []const u8, + name: [*:0]const u8, flags: option = .none, }; @@ -46,14 +44,13 @@ 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 = len, + .name_len = std.mem.span(nopts.name).len, .scrolling = nopts.flags == .VSCROLL, }; - @memcpy(plane.name_buf[0..len], nopts.name[0..len]); + @memcpy(plane.name_buf[0..plane.name_len], nopts.name); return plane; } diff --git a/src/renderer/vaxis/renderer.zig b/src/renderer/vaxis/renderer.zig index 0c5983d..08130db 100644 --- a/src/renderer/vaxis/renderer.zig +++ b/src/renderer/vaxis/renderer.zig @@ -57,7 +57,6 @@ 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 { @@ -136,46 +135,15 @@ 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(); } - 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); + @import("std/debug.zig").handleSegfaultPosix(sig, info, ctx_ptr); } pub fn run(self: *Self) Error!void { diff --git a/src/renderer/vaxis/std/debug.zig b/src/renderer/vaxis/std/debug.zig index b5f8697..8608184 100644 --- a/src/renderer/vaxis/std/debug.zig +++ b/src/renderer/vaxis/std/debug.zig @@ -1384,7 +1384,7 @@ pub fn attachSegfaultHandler() void { updateSegfaultHandler(&act); } -pub fn resetSegfaultHandler() void { +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(); } -pub fn dumpSegfaultInfoPosix(sig: i32, code: i32, addr: usize, ctx_ptr: ?*anyopaque) void { +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 diff --git a/src/renderer/win32/renderer.zig b/src/renderer/win32/renderer.zig index a56adb7..293ce68 100644 --- a/src/renderer/win32/renderer.zig +++ b/src/renderer/win32/renderer.zig @@ -35,7 +35,6 @@ pub const Error = error{ InvalidPIntType, JsonIncompatibleType, NotAnObject, - BadArrayAllocExtract, } || std.Thread.SpawnError; pub const panic = messageBoxThenPanic(.{ .title = "Flow Panic" }); diff --git a/src/shell.zig b/src/shell.zig index cb27221..69746cb 100644 --- a/src/shell.zig +++ b/src/shell.zig @@ -26,7 +26,6 @@ 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; diff --git a/src/syntax/build.zig.zon b/src/syntax/build.zig.zon index d8f5c7a..70ca5d8 100644 --- a/src/syntax/build.zig.zon +++ b/src/syntax/build.zig.zon @@ -6,12 +6,12 @@ .dependencies = .{ .tree_sitter = .{ - .url = "https://github.com/neurocyte/tree-sitter/releases/download/master-353313ad92324f2e9edc10082ce4768d49e44e7e/source.tar.gz", - .hash = "N-V-__8AAEU0UidDMndETXNGKGW66b0yAu58jXL5dmLbOcfH", + .url = "https://github.com/neurocyte/tree-sitter/releases/download/master-1c3ad59bd98ee430b166054030dac4c46d641e39/source.tar.gz", + .hash = "N-V-__8AANMzUiemOR2eNnrtlMmAGHFqij6VYtDUiaFfn6Dw", }, .cbor = .{ - .url = "https://github.com/neurocyte/cbor/archive/5ea4b7319146f29bb1aa9acf65982feaba9edc3d.tar.gz", - .hash = "cbor-1.0.0-RcQE_GDyAABovyRXoYFX8zD_NVOLGDc9l5g09-W-svMR", + .url = "https://github.com/neurocyte/cbor/archive/1fccb83c70cd84e1dff57cc53f7db8fb99909a94.tar.gz", + .hash = "cbor-1.0.0-RcQE_HvqAACcrLH7t3IDZOshgY2xqJA_UX330MvwSepb", }, }, .paths = .{ diff --git a/src/syntax/src/QueryCache.zig b/src/syntax/src/QueryCache.zig index 816d8bc..a9dad05 100644 --- a/src/syntax/src/QueryCache.zig +++ b/src/syntax/src/QueryCache.zig @@ -24,8 +24,7 @@ const CacheEntry = struct { query: ?*Query, query_arena: ?*std.heap.ArenaAllocator, query_type: QueryType, - file_type_name: []const u8, - lang_fn: FileType.LangFn, + file_type: *const FileType, fn destroy(self: *@This(), allocator: std.mem.Allocator) void { if (self.query_arena) |a| { @@ -102,7 +101,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: FileType, comptime query_type: QueryType) CacheError!*CacheEntry { +fn get_cache_entry(self: *Self, file_type: *const FileType, comptime query_type: QueryType) CacheError!*CacheEntry { if (self.mutex) |*mtx| mtx.lock(); defer if (self.mutex) |*mtx| mtx.unlock(); @@ -120,8 +119,7 @@ fn get_cache_entry(self: *Self, file_type: FileType, comptime query_type: QueryT .query = null, .query_arena = null, .mutex = if (self.mutex) |_| .{} else null, - .lang_fn = file_type.lang_fn, - .file_type_name = file_type.name, + .file_type = file_type, .query_type = query_type, }; entry_.value_ptr.* = q; @@ -135,8 +133,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.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.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 query_bin = switch (entry.query_type) { .highlights => queries.highlights_bin, .errors => queries.errors_bin, @@ -168,7 +166,7 @@ fn ReturnType(comptime query_type: QueryType) type { }; } -pub fn get(self: *Self, file_type: FileType, comptime query_type: QueryType) Error!ReturnType(query_type) { +pub fn get(self: *Self, file_type: *const 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))) { diff --git a/src/syntax/src/file_type.zig b/src/syntax/src/file_type.zig index 8030272..4c45c64 100644 --- a/src/syntax/src/file_type.zig +++ b/src/syntax/src/file_type.zig @@ -20,45 +20,43 @@ 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)) +pub fn get_by_name(name: []const u8) ?*const FileType { + for (file_types) |*file_type| + if (std.mem.eql(u8, file_type.name, name)) return file_type; return null; } -fn guess_first_line_static(content: []const u8) ?FileType { +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 { const first_line = if (std.mem.indexOf(u8, content, "\n")) |pos| content[0..pos] else content; - for (static_file_types) |*file_type| + for (file_types) |*file_type| if (file_type.first_line_matches) |match| - if (match_first_line(match.prefix, match.content, first_line)) + if (match_first_line(match, 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| +fn match_first_line(match: FirstLineMatch, first_line: []const u8) bool { + 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; } -pub fn match_file_type(extensions: []const []const u8, file_path: []const u8) bool { +fn match_file_type(file_type: *const FileType, 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| { + return for (file_type.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..])) @@ -87,15 +85,14 @@ fn ft_func_name(comptime lang: []const u8) []const u8 { return &func_name; } -pub const LangFn = *const fn () callconv(.C) ?*const treez.Language; +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.static_string_map.StaticStringMap(FileType).initComptime(static_file_type_list); +pub const file_types = load_file_types(@import("file_types.zig")); fn vec(comptime args: anytype) []const []const u8 { var cmd: []const []const u8 = &[_][]const u8{}; @@ -105,9 +102,7 @@ fn vec(comptime args: anytype) []const []const u8 { return cmd; } -const ListEntry = struct { []const u8, FileType }; - -fn load_file_types(comptime Namespace: type) []const ListEntry { +fn load_file_types(comptime Namespace: type) []const FileType { comptime switch (@typeInfo(Namespace)) { .@"struct" => |info| { var count = 0; @@ -115,12 +110,12 @@ fn load_file_types(comptime Namespace: type) []const ListEntry { // @compileLog(decl.name, @TypeOf(@field(Namespace, decl.name))); count += 1; } - var construct_types: [count]ListEntry = undefined; + 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] = .{ lang, .{ + construct_types[i] = .{ .color = if (@hasField(@TypeOf(args), "color")) args.color else 0xffffff, .icon = if (@hasField(@TypeOf(args), "icon")) args.icon else "󱀫", .name = lang, @@ -131,7 +126,7 @@ fn load_file_types(comptime Namespace: type) []const ListEntry { .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; diff --git a/src/syntax/src/syntax.zig b/src/syntax/src/syntax.zig index bd67ccf..2e5096e 100644 --- a/src/syntax/src/syntax.zig +++ b/src/syntax/src/syntax.zig @@ -21,13 +21,14 @@ 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: FileType, allocator: std.mem.Allocator, query_cache: *QueryCache) !*Self { +pub fn create(file_type: *const 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); @@ -35,6 +36,7 @@ pub fn create(file_type: FileType, allocator: std.mem.Allocator, query_cache: *Q 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, @@ -45,13 +47,13 @@ pub fn create(file_type: FileType, allocator: std.mem.Allocator, query_cache: *Q return self; } -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; +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; return create(file_type, allocator, query_cache); } -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; +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; return create(file_type, allocator, query_cache); } diff --git a/src/syntax/src/ts_serializer.zig b/src/syntax/src/ts_serializer.zig index 90c5865..973c1f7 100644 --- a/src/syntax/src/ts_serializer.zig +++ b/src/syntax/src/ts_serializer.zig @@ -280,7 +280,6 @@ pub const DeserializeError = error{ JsonIncompatibleType, InvalidQueryCbor, NotAnObject, - BadArrayAllocExtract, }; pub fn fromCbor(cb: []const u8, allocator: std.mem.Allocator) DeserializeError!struct { *TSQuery, *std.heap.ArenaAllocator } { diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 74fafd9..5b14afc 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -8,7 +8,6 @@ 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"); @@ -331,7 +330,6 @@ 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, @@ -581,30 +579,30 @@ pub const Editor = struct { try new_buf.root.store(content.writer(std.heap.c_allocator), new_buf.file_eol_mode); } - self.file_type = blk: { + const syn_file_type = blk: { const frame_ = tracy.initZone(@src(), .{ .name = "guess" }); defer frame_.deinit(); break :blk if (lang_override.len > 0) - try file_type_config.get(lang_override) + syntax.FileType.get_by_name(lang_override) else - file_type_config.guess_file_type(self.file_path, content.items); + syntax.FileType.guess(self.file_path, content.items); }; const syn = blk: { const frame_ = tracy.initZone(@src(), .{ .name = "create" }); defer frame_.deinit(); - break :blk if (self.file_type) |ft| - ft.create_syntax(self.allocator, tui.query_cache()) catch null + break :blk if (syn_file_type) |ft| + syntax.create(ft, self.allocator, tui.query_cache()) catch null else null; }; - if (self.file_type) |ft| { + if (syn) |syn_| { const frame_ = tracy.initZone(@src(), .{ .name = "did_open" }); defer frame_.deinit(); project_manager.did_open( file_path, - ft, + syn_.file_type, self.lsp_version, try content.toOwnedSlice(std.heap.c_allocator), new_buf.is_ephemeral(), @@ -616,9 +614,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.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; + 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; if (self.buffer) |buffer| { buffer.file_type_name = ftn; buffer.file_type_icon = fti; @@ -2948,46 +2946,30 @@ 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| { - 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; - }; + 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 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; - }; - } + 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; }; @@ -3610,7 +3592,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.file_type) |file_type| file_type.comment else "#"; + const comment = if (self.syntax) |syn| syn.file_type.comment else "//"; return self.toggle_prefix(command.fmt(.{comment})); } pub const toggle_comment_meta: Meta = .{ .description = "Toggle comment" }; @@ -3652,7 +3634,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 root; + if (first == 0) return error.Stop; 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; @@ -4649,7 +4631,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 = file_type_config.create_syntax_guess_file_type(self.allocator, content.items, self.file_path, tui.query_cache()) catch |e| switch (e) { + self.syntax = syntax.create_guess_file_type(self.allocator, content.items, self.file_path, tui.query_cache()) catch |e| switch (e) { error.NotFound => null, else => return e, }; @@ -5539,7 +5521,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.file_type) |file_type| if (file_type.formatter) |fmtr| if (fmtr.len > 0) return fmtr; + if (self.syntax) |syn| if (syn.file_type.formatter) |fmtr| if (fmtr.len > 0) return fmtr; return null; } @@ -5745,11 +5727,11 @@ pub const Editor = struct { saved.cursor = sel.end; break :ret sel; }; - var result = std.ArrayList(u8).init(self.allocator); - defer result.deinit(); + var result = std.ArrayListUnmanaged(u8).empty; + defer result.deinit(self.allocator); const writer: struct { self_: *Self, - result: *std.ArrayList(u8), + result: *std.ArrayListUnmanaged(u8), allocator: std.mem.Allocator, const Error = @typeInfo(@typeInfo(@TypeOf(Buffer.unicode.LetterCasing.toUpperStr)).@"fn".return_type.?).error_union.error_set; @@ -5760,7 +5742,7 @@ pub const Editor = struct { else try letter_casing.toLowerStr(writer.self_.allocator, bytes); defer writer.self_.allocator.free(flipped); - return writer.result.appendSlice(flipped); + return writer.result.appendSlice(writer.allocator, flipped); } fn map_error(e: anyerror, _: ?*std.builtin.StackTrace) Error { return @errorCast(e); @@ -5835,38 +5817,29 @@ pub const Editor = struct { self.syntax_refresh_full = true; self.syntax_incremental_reparse = false; - 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| { + self.syntax = syntax: { 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()); - - if (self.file_path) |file_path| + const syn = syntax.create_file_type(self.allocator, file_type, tui.query_cache()) catch null; + if (syn) |syn_| if (self.file_path) |file_path| project_manager.did_open( file_path, - ft, + syn_.file_type, 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.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 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 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}); diff --git a/src/tui/mainview.zig b/src/tui/mainview.zig index 7fafc9d..b94e64c 100644 --- a/src/tui/mainview.zig +++ b/src/tui/mainview.zig @@ -8,8 +8,6 @@ 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"); @@ -494,46 +492,6 @@ 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); diff --git a/src/tui/mode/overlay/file_type_palette.zig b/src/tui/mode/overlay/file_type_palette.zig index 90f1305..4c439cf 100644 --- a/src/tui/mode/overlay/file_type_palette.zig +++ b/src/tui/mode/overlay/file_type_palette.zig @@ -2,135 +2,129 @@ 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 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 Type = @import("palette.zig").Create(@This()); - pub const label = label_; - pub const name = " file type"; - pub const description = "file type"; +pub const label = "Select file type"; +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.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); - } +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; }; + + 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); } diff --git a/src/tui/tui.zig b/src/tui/tui.zig index 23633c8..b12c4ce 100644 --- a/src/tui/tui.zig +++ b/src/tui/tui.zig @@ -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 (@hasDecl(renderer, "install_crash_handler") and conf.start_debugger_on_crash) - renderer.jit_debugger_enabled = true; + if (conf.start_debugger_on_crash) + tp.install_debugger(); 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,6 +917,11 @@ 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(); @@ -1112,10 +1117,6 @@ 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_; }