Merge branch 'master' into zig-0.15.0
This commit is contained in:
commit
4e80bae8b8
24 changed files with 701 additions and 262 deletions
27
README.md
27
README.md
|
@ -94,14 +94,6 @@ You may install it on another system by simply copying the binary.
|
||||||
scp zig-out/bin/flow root@otherhost:/usr/local/bin
|
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:
|
Files to load may be specifed on the command line:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
@ -133,9 +125,26 @@ Show supported language names with `--list-languages`.
|
||||||
|
|
||||||
See `flow --help` for the full list of command line options.
|
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
|
# Key bindings and commands
|
||||||
|
|
||||||
Press `F2` to switch the current keybinding mode. (flow, vim, emacs, etc.)
|
Press `F4` to switch the current keybinding mode. (flow, vim, emacs, etc.)
|
||||||
Press `ctrl+shift+p` or `alt+x` to show the command palette.
|
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.
|
Press `ctrl+F2` to see a full list of all current keybindings and commands.
|
||||||
|
|
||||||
|
|
12
build.zig
12
build.zig
|
@ -317,6 +317,13 @@ 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(.{
|
const log_mod = b.createModule(.{
|
||||||
.root_source_file = b.path("src/log.zig"),
|
.root_source_file = b.path("src/log.zig"),
|
||||||
.imports = &.{
|
.imports = &.{
|
||||||
|
@ -498,7 +505,7 @@ pub fn build_exe(
|
||||||
.{ .name = "thespian", .module = thespian_mod },
|
.{ .name = "thespian", .module = thespian_mod },
|
||||||
.{ .name = "Buffer", .module = Buffer_mod },
|
.{ .name = "Buffer", .module = Buffer_mod },
|
||||||
.{ .name = "tracy", .module = tracy_mod },
|
.{ .name = "tracy", .module = tracy_mod },
|
||||||
.{ .name = "syntax", .module = syntax_mod },
|
.{ .name = "file_type_config", .module = file_type_config_mod },
|
||||||
.{ .name = "dizzy", .module = dizzy_dep.module("dizzy") },
|
.{ .name = "dizzy", .module = dizzy_dep.module("dizzy") },
|
||||||
.{ .name = "fuzzig", .module = fuzzig_dep.module("fuzzig") },
|
.{ .name = "fuzzig", .module = fuzzig_dep.module("fuzzig") },
|
||||||
.{ .name = "git", .module = git_mod },
|
.{ .name = "git", .module = git_mod },
|
||||||
|
@ -531,6 +538,7 @@ pub fn build_exe(
|
||||||
.{ .name = "cbor", .module = cbor_mod },
|
.{ .name = "cbor", .module = cbor_mod },
|
||||||
.{ .name = "config", .module = config_mod },
|
.{ .name = "config", .module = config_mod },
|
||||||
.{ .name = "gui_config", .module = gui_config_mod },
|
.{ .name = "gui_config", .module = gui_config_mod },
|
||||||
|
.{ .name = "file_type_config", .module = file_type_config_mod },
|
||||||
.{ .name = "log", .module = log_mod },
|
.{ .name = "log", .module = log_mod },
|
||||||
.{ .name = "command", .module = command_mod },
|
.{ .name = "command", .module = command_mod },
|
||||||
.{ .name = "EventHandler", .module = EventHandler_mod },
|
.{ .name = "EventHandler", .module = EventHandler_mod },
|
||||||
|
@ -583,6 +591,7 @@ pub fn build_exe(
|
||||||
exe.root_module.addImport("renderer", renderer_mod);
|
exe.root_module.addImport("renderer", renderer_mod);
|
||||||
exe.root_module.addImport("input", input_mod);
|
exe.root_module.addImport("input", input_mod);
|
||||||
exe.root_module.addImport("syntax", syntax_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("color", color_mod);
|
||||||
exe.root_module.addImport("bin_path", bin_path_mod);
|
exe.root_module.addImport("bin_path", bin_path_mod);
|
||||||
exe.root_module.addImport("version", b.createModule(.{ .root_source_file = version_file }));
|
exe.root_module.addImport("version", b.createModule(.{ .root_source_file = version_file }));
|
||||||
|
@ -627,6 +636,7 @@ pub fn build_exe(
|
||||||
check_exe.root_module.addImport("renderer", renderer_mod);
|
check_exe.root_module.addImport("renderer", renderer_mod);
|
||||||
check_exe.root_module.addImport("input", input_mod);
|
check_exe.root_module.addImport("input", input_mod);
|
||||||
check_exe.root_module.addImport("syntax", syntax_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("color", color_mod);
|
||||||
check_exe.root_module.addImport("bin_path", bin_path_mod);
|
check_exe.root_module.addImport("bin_path", bin_path_mod);
|
||||||
check_exe.root_module.addImport("version", b.createModule(.{ .root_source_file = version_file }));
|
check_exe.root_module.addImport("version", b.createModule(.{ .root_source_file = version_file }));
|
||||||
|
|
|
@ -15,8 +15,8 @@
|
||||||
.hash = "dizzy-1.0.0-AAAAAM1wAAAiDbx_6RwcVEOBk8p2XOu8t9WPNc3K7kBK",
|
.hash = "dizzy-1.0.0-AAAAAM1wAAAiDbx_6RwcVEOBk8p2XOu8t9WPNc3K7kBK",
|
||||||
},
|
},
|
||||||
.thespian = .{
|
.thespian = .{
|
||||||
.url = "https://github.com/neurocyte/thespian/archive/829a8d33e92988a51a8c51d204ec766a28c7903d.tar.gz",
|
.url = "https://github.com/neurocyte/thespian/archive/ccdcbbff09f945eec063ebf889581db3e1312107.tar.gz",
|
||||||
.hash = "thespian-0.0.1-owFOjs0TBgAAed7EtHDPtpB7NBn-riNjb7Rkc7a_Voow",
|
.hash = "thespian-0.0.1-owFOjlgaBgCqc3FCnB4Xyg8-9jyIDWgHSJMGx_nt5Kcc",
|
||||||
},
|
},
|
||||||
.themes = .{
|
.themes = .{
|
||||||
.url = "https://github.com/neurocyte/flow-themes/releases/download/master-952f9f630ea9544088fd30293666ee0650b7a690/flow-themes.tar.gz",
|
.url = "https://github.com/neurocyte/flow-themes/releases/download/master-952f9f630ea9544088fd30293666ee0650b7a690/flow-themes.tar.gz",
|
||||||
|
|
28
help.md
28
help.md
|
@ -157,19 +157,17 @@ Configuration is stored in the standard location
|
||||||
The default configuration will be written the first time
|
The default configuration will be written the first time
|
||||||
Flow Control is started and looks similar to this:
|
Flow Control is started and looks similar to this:
|
||||||
```
|
```
|
||||||
{
|
frame_rate 60
|
||||||
"frame_rate": 60,
|
theme "default"
|
||||||
"theme": "default",
|
input_mode "flow"
|
||||||
"input_mode": "flow",
|
gutter_line_numbers true
|
||||||
"gutter_line_numbers": true,
|
gutter_line_numbers_relative false
|
||||||
"gutter_line_numbers_relative": false,
|
enable_terminal_cursor false
|
||||||
"enable_terminal_cursor": false,
|
highlight_current_line true
|
||||||
"highlight_current_line": true,
|
highlight_current_line_gutter true
|
||||||
"highlight_current_line_gutter": true,
|
show_whitespace false
|
||||||
"show_whitespace": false,
|
animation_min_lag 0
|
||||||
"animation_min_lag": 0,
|
animation_max_lag 150
|
||||||
"animation_max_lag": 150
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Most of these options are fairly self explanitory.
|
Most of these options are fairly self explanitory.
|
||||||
|
@ -183,3 +181,7 @@ of frames rendered.
|
||||||
`animation_max_lag` controls the maximum amount of time allowed
|
`animation_max_lag` controls the maximum amount of time allowed
|
||||||
for rendering scrolling animations. Set to 0 to disable scrolling
|
for rendering scrolling animations. Set to 0 to disable scrolling
|
||||||
animation altogether.
|
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.
|
||||||
|
|
|
@ -230,6 +230,7 @@ pub fn restore_state_v0(self: *Self, data: []const u8) error{
|
||||||
InvalidPIntType,
|
InvalidPIntType,
|
||||||
JsonIncompatibleType,
|
JsonIncompatibleType,
|
||||||
NotAnObject,
|
NotAnObject,
|
||||||
|
BadArrayAllocExtract,
|
||||||
}!void {
|
}!void {
|
||||||
tp.trace(tp.channel.debug, .{"restore_state_v0"});
|
tp.trace(tp.channel.debug, .{"restore_state_v0"});
|
||||||
defer self.sort_files_by_mtime();
|
defer self.sort_files_by_mtime();
|
||||||
|
|
|
@ -23,10 +23,51 @@ pub const RGB = struct {
|
||||||
return .{ .r = v[0], .g = v[1], .b = v[2] };
|
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 {
|
pub fn to_u8s(v: RGB) [3]u8 {
|
||||||
return [_]u8{ v.r, v.g, v.b };
|
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 {
|
pub fn contrast(a_: RGB, b_: RGB) f32 {
|
||||||
const a = RGBf.from_RGB(a_).luminance();
|
const a = RGBf.from_RGB(a_).luminance();
|
||||||
const b = RGBf.from_RGB(b_).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"],
|
"deinit_command": ["resume_undo_history"],
|
||||||
"press": [
|
"press": [
|
||||||
["<Esc>", ["move_left_vim"], ["enter_mode", "normal"]],
|
["<Esc>", ["move_left_vim"], ["enter_mode", "normal"]],
|
||||||
|
["<C-c>", ["move_left_vim"], ["enter_mode", "normal"]],
|
||||||
["<Del>", "delete_forward"],
|
["<Del>", "delete_forward"],
|
||||||
["<BS>", "delete_backward"],
|
["<BS>", "delete_backward"],
|
||||||
["<CR>", "smart_insert_line"],
|
["<CR>", "smart_insert_line"],
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const syntax = @import("syntax");
|
const file_type_config = @import("file_type_config");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const RGB = @import("color").RGB;
|
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_formatter_len: usize = 0;
|
||||||
var max_extensions_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_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_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));
|
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 tty_config.setColor(writer, .reset);
|
||||||
try writer.writeAll("\n");
|
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 writer.writeAll(" ");
|
||||||
try setColorRgb(writer, file_type.color);
|
try setColorRgb(writer, file_type.color orelse file_type_config.default.color);
|
||||||
try writer.writeAll(file_type.icon);
|
try writer.writeAll(file_type.icon orelse file_type_config.default.icon);
|
||||||
try tty_config.setColor(writer, .reset);
|
try tty_config.setColor(writer, .reset);
|
||||||
try writer.writeAll(" ");
|
try writer.writeAll(" ");
|
||||||
try write_string(writer, file_type.name, max_language_len + 1);
|
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 tui = @import("tui");
|
||||||
const cbor = @import("cbor");
|
const cbor = @import("cbor");
|
||||||
const thespian = @import("thespian");
|
const thespian = @import("thespian");
|
||||||
|
const color = @import("color");
|
||||||
const flags = @import("flags");
|
const flags = @import("flags");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const bin_path = @import("bin_path");
|
const bin_path = @import("bin_path");
|
||||||
|
@ -148,11 +149,10 @@ pub fn main() anyerror!void {
|
||||||
return list_languages.list(a, stdout.writer(), tty_config);
|
return list_languages.list(a, stdout.writer(), tty_config);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (builtin.os.tag != .windows)
|
if (builtin.os.tag != .windows and @hasDecl(renderer, "install_crash_handler")) {
|
||||||
if (std.posix.getenv("JITDEBUG")) |_|
|
if (std.posix.getenv("JITDEBUG")) |_| renderer.jit_debugger_enabled = true;
|
||||||
thespian.install_debugger()
|
renderer.install_crash_handler();
|
||||||
else if (@hasDecl(renderer, "install_crash_handler"))
|
}
|
||||||
renderer.install_crash_handler();
|
|
||||||
|
|
||||||
if (args.debug_wait) {
|
if (args.debug_wait) {
|
||||||
std.debug.print("press return to start", .{});
|
std.debug.print("press return to start", .{});
|
||||||
|
@ -445,13 +445,24 @@ pub fn exists_config(T: type) bool {
|
||||||
return true;
|
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 } {
|
pub fn read_config(T: type, allocator: std.mem.Allocator) struct { T, [][]const u8 } {
|
||||||
config_mutex.lock();
|
config_mutex.lock();
|
||||||
defer config_mutex.unlock();
|
defer config_mutex.unlock();
|
||||||
var bufs: [][]const u8 = &[_][]const u8{};
|
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];
|
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)) {
|
if (!read_config_file(T, allocator, &conf, &bufs, text_file_name)) {
|
||||||
_ = read_config_file(T, allocator, &conf, &bufs, json_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 {
|
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 });
|
var file = try std.fs.openFileAbsolute(file_name, .{ .mode = .read_only });
|
||||||
defer file.close();
|
defer file.close();
|
||||||
const text = try file.readToEndAlloc(allocator, 64 * 1024);
|
const content = try file.readToEndAlloc(allocator, 64 * 1024);
|
||||||
defer allocator.free(text);
|
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);
|
var cbor_buf = std.ArrayList(u8).init(allocator);
|
||||||
defer cbor_buf.deinit();
|
defer cbor_buf.deinit();
|
||||||
const writer = cbor_buf.writer();
|
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;
|
var lineno: u32 = 0;
|
||||||
while (it.next()) |line| {
|
while (it.next()) |line| {
|
||||||
lineno += 1;
|
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_.*);
|
var bufs = std.ArrayListUnmanaged([]const u8).fromOwnedSlice(bufs_.*);
|
||||||
bufs.append(allocator, cb) catch @panic("OOM:read_text_config_file");
|
bufs.append(allocator, cb) catch @panic("OOM:read_text_config_file");
|
||||||
bufs_.* = bufs.toOwnedSlice(allocator) 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 {
|
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);
|
const cb = try cbor.fromJson(json, cbor_buf);
|
||||||
var iter = cb;
|
var iter = cb;
|
||||||
_ = try cbor.decodeMapHeader(&iter);
|
_ = 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(
|
fn read_cbor_config(
|
||||||
T: type,
|
T: type,
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
conf: *T,
|
conf: *T,
|
||||||
file_name: []const u8,
|
file_name: []const u8,
|
||||||
cb: []const u8,
|
cb: []const u8,
|
||||||
|
@ -554,12 +570,29 @@ fn read_cbor_config(
|
||||||
}
|
}
|
||||||
} else if (std.mem.eql(u8, field_name, field_info.name)) {
|
} else if (std.mem.eql(u8, field_name, field_info.name)) {
|
||||||
known = true;
|
known = true;
|
||||||
var value: field_info.type = undefined;
|
switch (field_info.type) {
|
||||||
if (try cbor.matchValue(&iter, cbor.extract(&value))) {
|
u24, ?u24 => {
|
||||||
@field(conf, field_info.name) = value;
|
var value: []const u8 = undefined;
|
||||||
} else {
|
if (try cbor.matchValue(&iter, cbor.extract(&value))) {
|
||||||
try cbor.skipValue(&iter);
|
const color_ = color.RGB.from_string(value);
|
||||||
std.log.err("invalid value for key '{s}'", .{field_name});
|
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) {
|
if (!known) {
|
||||||
|
@ -613,15 +646,36 @@ pub fn write_config_to_writer(comptime T: type, data: T, writer: anytype) @TypeO
|
||||||
} else {
|
} else {
|
||||||
try writer.print("{s} ", .{field_info.name});
|
try writer.print("{s} ", .{field_info.name});
|
||||||
}
|
}
|
||||||
var s = std.json.writeStream(writer, .{ .whitespace = .indent_4 });
|
switch (field_info.type) {
|
||||||
try s.write(@field(data, field_info.name));
|
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", .{});
|
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 {
|
fn config_eql(comptime T: type, a: T, b: T) bool {
|
||||||
switch (T) {
|
switch (T) {
|
||||||
[]const u8 => return std.mem.eql(u8, a, b),
|
[]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 => {},
|
else => {},
|
||||||
}
|
}
|
||||||
switch (@typeInfo(T)) {
|
switch (@typeInfo(T)) {
|
||||||
|
@ -707,7 +761,7 @@ pub fn list_themes(allocator: std.mem.Allocator) ![]const []const u8 {
|
||||||
return result.toOwnedSlice();
|
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);
|
return get_app_config_dir(application_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ const tp = @import("thespian");
|
||||||
const cbor = @import("cbor");
|
const cbor = @import("cbor");
|
||||||
const log = @import("log");
|
const log = @import("log");
|
||||||
const tracy = @import("tracy");
|
const tracy = @import("tracy");
|
||||||
const FileType = @import("syntax").FileType;
|
const file_type_config = @import("file_type_config");
|
||||||
const root = @import("root");
|
const root = @import("root");
|
||||||
const Buffer = @import("Buffer");
|
const Buffer = @import("Buffer");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
|
@ -137,7 +137,7 @@ pub fn delete_task(task: []const u8) (ProjectManagerError || ProjectError)!void
|
||||||
return send(.{ "delete_task", project, task });
|
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;
|
if (ephemeral) return;
|
||||||
const project = tp.env.get().str("project");
|
const project = tp.env.get().str("project");
|
||||||
if (project.len == 0)
|
if (project.len == 0)
|
||||||
|
|
|
@ -11,10 +11,12 @@ const RGB = @import("color").RGB;
|
||||||
|
|
||||||
const Plane = @This();
|
const Plane = @This();
|
||||||
|
|
||||||
|
const name_buf_len = 128;
|
||||||
|
|
||||||
window: vaxis.Window,
|
window: vaxis.Window,
|
||||||
row: i32 = 0,
|
row: i32 = 0,
|
||||||
col: i32 = 0,
|
col: i32 = 0,
|
||||||
name_buf: [128]u8,
|
name_buf: [name_buf_len]u8,
|
||||||
name_len: usize,
|
name_len: usize,
|
||||||
cache: GraphemeCache = .{},
|
cache: GraphemeCache = .{},
|
||||||
style: vaxis.Cell.Style = .{},
|
style: vaxis.Cell.Style = .{},
|
||||||
|
@ -27,7 +29,7 @@ pub const Options = struct {
|
||||||
x: usize = 0,
|
x: usize = 0,
|
||||||
rows: usize = 0,
|
rows: usize = 0,
|
||||||
cols: usize = 0,
|
cols: usize = 0,
|
||||||
name: [*:0]const u8,
|
name: []const u8,
|
||||||
flags: option = .none,
|
flags: option = .none,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -44,13 +46,14 @@ pub fn init(nopts: *const Options, parent_: Plane) !Plane {
|
||||||
.height = @as(u16, @intCast(nopts.rows)),
|
.height = @as(u16, @intCast(nopts.rows)),
|
||||||
.border = .{},
|
.border = .{},
|
||||||
};
|
};
|
||||||
|
const len = @min(nopts.name.len, name_buf_len);
|
||||||
var plane: Plane = .{
|
var plane: Plane = .{
|
||||||
.window = parent_.window.child(opts),
|
.window = parent_.window.child(opts),
|
||||||
.name_buf = undefined,
|
.name_buf = undefined,
|
||||||
.name_len = std.mem.span(nopts.name).len,
|
.name_len = len,
|
||||||
.scrolling = nopts.flags == .VSCROLL,
|
.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;
|
return plane;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,6 +57,7 @@ pub const Error = error{
|
||||||
InvalidPIntType,
|
InvalidPIntType,
|
||||||
JsonIncompatibleType,
|
JsonIncompatibleType,
|
||||||
NotAnObject,
|
NotAnObject,
|
||||||
|
BadArrayAllocExtract,
|
||||||
} || std.Thread.SpawnError;
|
} || std.Thread.SpawnError;
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator, handler_ctx: *anyopaque, no_alternate: bool, _: *const fn (ctx: *anyopaque) void) Error!Self {
|
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);
|
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 {
|
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);
|
in_panic.store(true, .release);
|
||||||
const cleanup = panic_cleanup;
|
const cleanup = panic_cleanup;
|
||||||
panic_cleanup = null;
|
panic_cleanup = null;
|
||||||
|
|
||||||
if (cleanup) |self| {
|
if (cleanup) |self| {
|
||||||
self.vx.deinit(self.allocator, self.tty.anyWriter());
|
self.vx.deinit(self.allocator, self.tty.anyWriter());
|
||||||
self.tty.deinit();
|
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 {
|
pub fn run(self: *Self) Error!void {
|
||||||
|
|
|
@ -1384,7 +1384,7 @@ pub fn attachSegfaultHandler() void {
|
||||||
updateSegfaultHandler(&act);
|
updateSegfaultHandler(&act);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resetSegfaultHandler() void {
|
pub fn resetSegfaultHandler() void {
|
||||||
if (native_os == .windows) {
|
if (native_os == .windows) {
|
||||||
if (windows_segfault_handle) |handle| {
|
if (windows_segfault_handle) |handle| {
|
||||||
assert(windows.kernel32.RemoveVectoredExceptionHandler(handle) != 0);
|
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();
|
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();
|
const stderr = io.getStdErr().writer();
|
||||||
_ = switch (sig) {
|
_ = switch (sig) {
|
||||||
posix.SIG.SEGV => if (native_arch == .x86_64 and native_os == .linux and code == 128) // SI_KERNEL
|
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,
|
InvalidPIntType,
|
||||||
JsonIncompatibleType,
|
JsonIncompatibleType,
|
||||||
NotAnObject,
|
NotAnObject,
|
||||||
|
BadArrayAllocExtract,
|
||||||
} || std.Thread.SpawnError;
|
} || std.Thread.SpawnError;
|
||||||
|
|
||||||
pub const panic = messageBoxThenPanic(.{ .title = "Flow Panic" });
|
pub const panic = messageBoxThenPanic(.{ .title = "Flow Panic" });
|
||||||
|
|
|
@ -26,6 +26,7 @@ pub const Error = error{
|
||||||
InvalidPIntType,
|
InvalidPIntType,
|
||||||
JsonIncompatibleType,
|
JsonIncompatibleType,
|
||||||
NotAnObject,
|
NotAnObject,
|
||||||
|
BadArrayAllocExtract,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const OutputHandler = fn (context: usize, parent: tp.pid_ref, arg0: []const u8, output: []const u8) void;
|
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: ?*Query,
|
||||||
query_arena: ?*std.heap.ArenaAllocator,
|
query_arena: ?*std.heap.ArenaAllocator,
|
||||||
query_type: QueryType,
|
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 {
|
fn destroy(self: *@This(), allocator: std.mem.Allocator) void {
|
||||||
if (self.query_arena) |a| {
|
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);
|
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();
|
if (self.mutex) |*mtx| mtx.lock();
|
||||||
defer if (self.mutex) |*mtx| mtx.unlock();
|
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 = null,
|
||||||
.query_arena = null,
|
.query_arena = null,
|
||||||
.mutex = if (self.mutex) |_| .{} else 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,
|
.query_type = query_type,
|
||||||
};
|
};
|
||||||
entry_.value_ptr.* = q;
|
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();
|
defer if (entry.mutex) |*mtx| mtx.unlock();
|
||||||
|
|
||||||
return if (entry.query) |query| query else blk: {
|
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 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 queries = FileType.queries.get(entry.file_type_name) orelse return null;
|
||||||
const query_bin = switch (entry.query_type) {
|
const query_bin = switch (entry.query_type) {
|
||||||
.highlights => queries.highlights_bin,
|
.highlights => queries.highlights_bin,
|
||||||
.errors => queries.errors_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));
|
const query = try self.get_cached_query(try self.get_cache_entry(file_type, query_type));
|
||||||
self.add_ref_locked();
|
self.add_ref_locked();
|
||||||
return switch (@typeInfo(ReturnType(query_type))) {
|
return switch (@typeInfo(ReturnType(query_type))) {
|
||||||
|
|
|
@ -20,43 +20,45 @@ comment: []const u8,
|
||||||
formatter: ?[]const []const u8,
|
formatter: ?[]const []const u8,
|
||||||
language_server: ?[]const []const u8,
|
language_server: ?[]const []const u8,
|
||||||
|
|
||||||
pub fn get_by_name(name: []const u8) ?*const FileType {
|
pub fn get_by_name_static(name: []const u8) ?FileType {
|
||||||
for (file_types) |*file_type|
|
return FileType.static_file_types.get(name);
|
||||||
if (std.mem.eql(u8, file_type.name, 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 file_type;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn guess(file_path: ?[]const u8, content: []const u8) ?*const FileType {
|
fn guess_first_line_static(content: []const u8) ?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;
|
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 (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 file_type;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn match_first_line(match: FirstLineMatch, first_line: []const u8) bool {
|
pub fn match_first_line(match_prefix: ?[]const u8, match_content: ?[]const u8, first_line: []const u8) bool {
|
||||||
if (match.prefix) |prefix|
|
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))
|
if (prefix.len > first_line.len or !std.mem.eql(u8, first_line[0..prefix.len], prefix))
|
||||||
return false;
|
return false;
|
||||||
if (match.content) |content|
|
if (match_content) |content|
|
||||||
if (std.mem.indexOf(u8, first_line, content)) |_| {} else return false;
|
if (std.mem.indexOf(u8, first_line, content)) |_| {} else return false;
|
||||||
return true;
|
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 basename = std.fs.path.basename(file_path);
|
||||||
const extension = std.fs.path.extension(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))
|
if (ext.len == basename.len and std.mem.eql(u8, ext, basename))
|
||||||
return true;
|
return true;
|
||||||
if (extension.len > 0 and ext.len == extension.len - 1 and std.mem.eql(u8, ext, extension[1..]))
|
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;
|
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 {
|
pub const FirstLineMatch = struct {
|
||||||
prefix: ?[]const u8 = null,
|
prefix: ?[]const u8 = null,
|
||||||
content: ?[]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 {
|
fn vec(comptime args: anytype) []const []const u8 {
|
||||||
var cmd: []const []const u8 = &[_][]const u8{};
|
var cmd: []const []const u8 = &[_][]const u8{};
|
||||||
|
@ -102,7 +105,9 @@ fn vec(comptime args: anytype) []const []const u8 {
|
||||||
return cmd;
|
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)) {
|
comptime switch (@typeInfo(Namespace)) {
|
||||||
.@"struct" => |info| {
|
.@"struct" => |info| {
|
||||||
var count = 0;
|
var count = 0;
|
||||||
|
@ -110,12 +115,12 @@ fn load_file_types(comptime Namespace: type) []const FileType {
|
||||||
// @compileLog(decl.name, @TypeOf(@field(Namespace, decl.name)));
|
// @compileLog(decl.name, @TypeOf(@field(Namespace, decl.name)));
|
||||||
count += 1;
|
count += 1;
|
||||||
}
|
}
|
||||||
var construct_types: [count]FileType = undefined;
|
var construct_types: [count]ListEntry = undefined;
|
||||||
var i = 0;
|
var i = 0;
|
||||||
for (info.decls) |decl| {
|
for (info.decls) |decl| {
|
||||||
const lang = decl.name;
|
const lang = decl.name;
|
||||||
const args = @field(Namespace, lang);
|
const args = @field(Namespace, lang);
|
||||||
construct_types[i] = .{
|
construct_types[i] = .{ lang, .{
|
||||||
.color = if (@hasField(@TypeOf(args), "color")) args.color else 0xffffff,
|
.color = if (@hasField(@TypeOf(args), "color")) args.color else 0xffffff,
|
||||||
.icon = if (@hasField(@TypeOf(args), "icon")) args.icon else "",
|
.icon = if (@hasField(@TypeOf(args), "icon")) args.icon else "",
|
||||||
.name = lang,
|
.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,
|
.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,
|
.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,
|
.language_server = if (@hasField(@TypeOf(args), "language_server")) vec(args.language_server) else null,
|
||||||
};
|
} };
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
const types = construct_types;
|
const types = construct_types;
|
||||||
|
|
|
@ -21,14 +21,13 @@ pub const Node = treez.Node;
|
||||||
|
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
lang: *const Language,
|
lang: *const Language,
|
||||||
file_type: *const FileType,
|
|
||||||
parser: *Parser,
|
parser: *Parser,
|
||||||
query: *Query,
|
query: *Query,
|
||||||
errors_query: *Query,
|
errors_query: *Query,
|
||||||
injections: ?*Query,
|
injections: ?*Query,
|
||||||
tree: ?*treez.Tree = null,
|
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 query = try query_cache.get(file_type, .highlights);
|
||||||
const errors_query = try query_cache.get(file_type, .errors);
|
const errors_query = try query_cache.get(file_type, .errors);
|
||||||
const injections = try query_cache.get(file_type, .injections);
|
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.* = .{
|
self.* = .{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.lang = file_type.lang_fn() orelse std.debug.panic("tree-sitter parser function failed for language: {s}", .{file_type.name}),
|
.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(),
|
.parser = try Parser.create(),
|
||||||
.query = query,
|
.query = query,
|
||||||
.errors_query = errors_query,
|
.errors_query = errors_query,
|
||||||
|
@ -47,13 +45,13 @@ pub fn create(file_type: *const FileType, allocator: std.mem.Allocator, query_ca
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_file_type(allocator: std.mem.Allocator, lang_name: []const u8, query_cache: *QueryCache) !*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(lang_name) orelse return error.NotFound;
|
const file_type = FileType.get_by_name_static(lang_name) orelse return error.NotFound;
|
||||||
return create(file_type, allocator, query_cache);
|
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 {
|
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(file_path, content) orelse return error.NotFound;
|
const file_type = FileType.guess_static(file_path, content) orelse return error.NotFound;
|
||||||
return create(file_type, allocator, query_cache);
|
return create(file_type, allocator, query_cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -280,6 +280,7 @@ pub const DeserializeError = error{
|
||||||
JsonIncompatibleType,
|
JsonIncompatibleType,
|
||||||
InvalidQueryCbor,
|
InvalidQueryCbor,
|
||||||
NotAnObject,
|
NotAnObject,
|
||||||
|
BadArrayAllocExtract,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn fromCbor(cb: []const u8, allocator: std.mem.Allocator) DeserializeError!struct { *TSQuery, *std.heap.ArenaAllocator } {
|
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 tracy = @import("tracy");
|
||||||
const text_manip = @import("text_manip");
|
const text_manip = @import("text_manip");
|
||||||
const syntax = @import("syntax");
|
const syntax = @import("syntax");
|
||||||
|
const file_type_config = @import("file_type_config");
|
||||||
const project_manager = @import("project_manager");
|
const project_manager = @import("project_manager");
|
||||||
const root_mod = @import("root");
|
const root_mod = @import("root");
|
||||||
|
|
||||||
|
@ -330,6 +331,7 @@ pub const Editor = struct {
|
||||||
utf8_sanitized: bool = false,
|
utf8_sanitized: bool = false,
|
||||||
} = .{},
|
} = .{},
|
||||||
|
|
||||||
|
file_type: ?file_type_config = null,
|
||||||
syntax: ?*syntax = null,
|
syntax: ?*syntax = null,
|
||||||
syntax_no_render: bool = false,
|
syntax_no_render: bool = false,
|
||||||
syntax_report_timing: 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);
|
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" });
|
const frame_ = tracy.initZone(@src(), .{ .name = "guess" });
|
||||||
defer frame_.deinit();
|
defer frame_.deinit();
|
||||||
break :blk if (lang_override.len > 0)
|
break :blk if (lang_override.len > 0)
|
||||||
syntax.FileType.get_by_name(lang_override)
|
try file_type_config.get(lang_override)
|
||||||
else
|
else
|
||||||
syntax.FileType.guess(self.file_path, content.items);
|
file_type_config.guess_file_type(self.file_path, content.items);
|
||||||
};
|
};
|
||||||
|
|
||||||
const syn = blk: {
|
const syn = blk: {
|
||||||
const frame_ = tracy.initZone(@src(), .{ .name = "create" });
|
const frame_ = tracy.initZone(@src(), .{ .name = "create" });
|
||||||
defer frame_.deinit();
|
defer frame_.deinit();
|
||||||
break :blk if (syn_file_type) |ft|
|
break :blk if (self.file_type) |ft|
|
||||||
syntax.create(ft, self.allocator, tui.query_cache()) catch null
|
ft.create_syntax(self.allocator, tui.query_cache()) catch null
|
||||||
else
|
else
|
||||||
null;
|
null;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (syn) |syn_| {
|
if (self.file_type) |ft| {
|
||||||
const frame_ = tracy.initZone(@src(), .{ .name = "did_open" });
|
const frame_ = tracy.initZone(@src(), .{ .name = "did_open" });
|
||||||
defer frame_.deinit();
|
defer frame_.deinit();
|
||||||
project_manager.did_open(
|
project_manager.did_open(
|
||||||
file_path,
|
file_path,
|
||||||
syn_.file_type,
|
ft,
|
||||||
self.lsp_version,
|
self.lsp_version,
|
||||||
try content.toOwnedSlice(std.heap.c_allocator),
|
try content.toOwnedSlice(std.heap.c_allocator),
|
||||||
new_buf.is_ephemeral(),
|
new_buf.is_ephemeral(),
|
||||||
|
@ -614,9 +616,9 @@ pub const Editor = struct {
|
||||||
self.syntax_no_render = tp.env.get().is("no-syntax");
|
self.syntax_no_render = tp.env.get().is("no-syntax");
|
||||||
self.syntax_report_timing = tp.env.get().is("syntax-report-timing");
|
self.syntax_report_timing = tp.env.get().is("syntax-report-timing");
|
||||||
|
|
||||||
const ftn = if (self.syntax) |syn| syn.file_type.name else "text";
|
const ftn = if (self.file_type) |ft| ft.name else file_type_config.default.name;
|
||||||
const fti = if (self.syntax) |syn| syn.file_type.icon else "🖹";
|
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.syntax) |syn| syn.file_type.color else 0x000000;
|
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| {
|
if (self.buffer) |buffer| {
|
||||||
buffer.file_type_name = ftn;
|
buffer.file_type_name = ftn;
|
||||||
buffer.file_type_icon = fti;
|
buffer.file_type_icon = fti;
|
||||||
|
@ -2946,30 +2948,46 @@ pub const Editor = struct {
|
||||||
|
|
||||||
for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel| {
|
for (self.cursels.items) |*cursel_| if (cursel_.*) |*cursel| {
|
||||||
if (cursel.selection) |_| {
|
if (cursel.selection) |_| {
|
||||||
|
// just delete selection
|
||||||
root = self.delete_selection(root, cursel, b.allocator) catch continue;
|
root = self.delete_selection(root, cursel, b.allocator) catch continue;
|
||||||
all_stop = false;
|
all_stop = false;
|
||||||
continue;
|
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;
|
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 (cursel.selection) |*sel| {
|
||||||
const egc_left, _, _ = sel.end.egc_at(root, self.metrics) catch {
|
if (first > sel.end.col) {
|
||||||
root = self.delete_selection(root, cursel, b.allocator) catch continue;
|
// we are inside leading whitespace
|
||||||
all_stop = false;
|
// select to next indentation boundary
|
||||||
continue;
|
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;
|
||||||
const egc_right, _, _ = sel.begin.egc_at(root, self.metrics) catch {
|
} else {
|
||||||
root = self.delete_selection(root, cursel, b.allocator) catch continue;
|
// char being deleted
|
||||||
all_stop = false;
|
const egc_left, _, _ = sel.end.egc_at(root, self.metrics) catch {
|
||||||
continue;
|
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])) {
|
// if left char is a smart pair left char, also delete smart pair right char
|
||||||
sel.begin.move_right(root, self.metrics) catch {};
|
for (Buffer.unicode.char_pairs) |pair| if (std.mem.eql(u8, egc_left, pair[0]) and std.mem.eql(u8, egc_right, pair[1])) {
|
||||||
break;
|
sel.begin.move_right(root, self.metrics) catch {};
|
||||||
};
|
break;
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
root = self.delete_selection(root, cursel, b.allocator) catch continue;
|
root = self.delete_selection(root, cursel, b.allocator) catch continue;
|
||||||
all_stop = false;
|
all_stop = false;
|
||||||
};
|
};
|
||||||
|
@ -3592,7 +3610,7 @@ pub const Editor = struct {
|
||||||
pub const toggle_prefix_meta: Meta = .{ .arguments = &.{.string} };
|
pub const toggle_prefix_meta: Meta = .{ .arguments = &.{.string} };
|
||||||
|
|
||||||
pub fn toggle_comment(self: *Self, _: Context) Result {
|
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}));
|
return self.toggle_prefix(command.fmt(.{comment}));
|
||||||
}
|
}
|
||||||
pub const toggle_comment_meta: Meta = .{ .description = "Toggle comment" };
|
pub const toggle_comment_meta: Meta = .{ .description = "Toggle comment" };
|
||||||
|
@ -3634,7 +3652,7 @@ pub const Editor = struct {
|
||||||
var cursel: CurSel = .{};
|
var cursel: CurSel = .{};
|
||||||
cursel.cursor = cursor.*;
|
cursel.cursor = cursor.*;
|
||||||
const first = find_first_non_ws(root, cursel.cursor.row, self.metrics);
|
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 off = first % self.indent_size;
|
||||||
const cols = if (off == 0) self.indent_size else off;
|
const cols = if (off == 0) self.indent_size else off;
|
||||||
const sel = cursel.enable_selection(root, self.metrics) catch return error.Stop;
|
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;
|
var content = std.ArrayListUnmanaged(u8).empty;
|
||||||
defer content.deinit(self.allocator);
|
defer content.deinit(self.allocator);
|
||||||
try root.store(content.writer(self.allocator), eol_mode);
|
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,
|
error.NotFound => null,
|
||||||
else => return e,
|
else => return e,
|
||||||
};
|
};
|
||||||
|
@ -5521,7 +5539,7 @@ pub const Editor = struct {
|
||||||
pub const select_meta: Meta = .{ .arguments = &.{ .integer, .integer, .integer, .integer } };
|
pub const select_meta: Meta = .{ .arguments = &.{ .integer, .integer, .integer, .integer } };
|
||||||
|
|
||||||
fn get_formatter(self: *Self) ?[]const []const u8 {
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5727,11 +5745,11 @@ pub const Editor = struct {
|
||||||
saved.cursor = sel.end;
|
saved.cursor = sel.end;
|
||||||
break :ret sel;
|
break :ret sel;
|
||||||
};
|
};
|
||||||
var result = std.ArrayListUnmanaged(u8).empty;
|
var result = std.ArrayList(u8).init(self.allocator);
|
||||||
defer result.deinit(self.allocator);
|
defer result.deinit();
|
||||||
const writer: struct {
|
const writer: struct {
|
||||||
self_: *Self,
|
self_: *Self,
|
||||||
result: *std.ArrayListUnmanaged(u8),
|
result: *std.ArrayList(u8),
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
|
|
||||||
const Error = @typeInfo(@typeInfo(@TypeOf(Buffer.unicode.LetterCasing.toUpperStr)).@"fn".return_type.?).error_union.error_set;
|
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
|
else
|
||||||
try letter_casing.toLowerStr(writer.self_.allocator, bytes);
|
try letter_casing.toLowerStr(writer.self_.allocator, bytes);
|
||||||
defer writer.self_.allocator.free(flipped);
|
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 {
|
fn map_error(e: anyerror, _: ?*std.builtin.StackTrace) Error {
|
||||||
return @errorCast(e);
|
return @errorCast(e);
|
||||||
|
@ -5817,29 +5835,38 @@ pub const Editor = struct {
|
||||||
self.syntax_refresh_full = true;
|
self.syntax_refresh_full = true;
|
||||||
self.syntax_incremental_reparse = false;
|
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;
|
var content = std.ArrayListUnmanaged(u8).empty;
|
||||||
defer content.deinit(std.heap.c_allocator);
|
defer content.deinit(std.heap.c_allocator);
|
||||||
const root = try self.buf_root();
|
const root = try self.buf_root();
|
||||||
try root.store(content.writer(std.heap.c_allocator), try self.buf_eol_mode());
|
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(
|
project_manager.did_open(
|
||||||
file_path,
|
file_path,
|
||||||
syn_.file_type,
|
ft,
|
||||||
self.lsp_version,
|
self.lsp_version,
|
||||||
try content.toOwnedSlice(std.heap.c_allocator),
|
try content.toOwnedSlice(std.heap.c_allocator),
|
||||||
if (self.buffer) |p| p.is_ephemeral() else true,
|
if (self.buffer) |p| p.is_ephemeral() else true,
|
||||||
) catch |e|
|
) catch |e|
|
||||||
self.logger.print("project_manager.did_open failed: {any}", .{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_no_render = tp.env.get().is("no-syntax");
|
||||||
self.syntax_report_timing = tp.env.get().is("syntax-report-timing");
|
self.syntax_report_timing = tp.env.get().is("syntax-report-timing");
|
||||||
|
|
||||||
const ftn = if (self.syntax) |syn| syn.file_type.name else "text";
|
const ftn = if (self.file_type) |ft| ft.name else "text";
|
||||||
const fti = if (self.syntax) |syn| syn.file_type.icon else "🖹";
|
const fti = if (self.file_type) |ft| ft.icon orelse "🖹" else "🖹";
|
||||||
const ftc = if (self.syntax) |syn| syn.file_type.color else 0x000000;
|
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;
|
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);
|
try self.send_editor_open(self.file_path orelse "", file_exists, ftn, fti, ftc);
|
||||||
self.logger.print("file type {s}", .{file_type});
|
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 project_manager = @import("project_manager");
|
||||||
const log = @import("log");
|
const log = @import("log");
|
||||||
const shell = @import("shell");
|
const shell = @import("shell");
|
||||||
|
const syntax = @import("syntax");
|
||||||
|
const file_type_config = @import("file_type_config");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const build_options = @import("build_options");
|
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 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 {
|
pub fn create_scratch_buffer(self: *Self, ctx: Ctx) Result {
|
||||||
const args = try ctx.args.clone(self.allocator);
|
const args = try ctx.args.clone(self.allocator);
|
||||||
defer self.allocator.free(args.buf);
|
defer self.allocator.free(args.buf);
|
||||||
|
|
|
@ -2,129 +2,135 @@ const std = @import("std");
|
||||||
const cbor = @import("cbor");
|
const cbor = @import("cbor");
|
||||||
const tp = @import("thespian");
|
const tp = @import("thespian");
|
||||||
const syntax = @import("syntax");
|
const syntax = @import("syntax");
|
||||||
|
const file_type_config = @import("file_type_config");
|
||||||
|
|
||||||
const Widget = @import("../../Widget.zig");
|
const Widget = @import("../../Widget.zig");
|
||||||
const tui = @import("../../tui.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 label = label_;
|
||||||
pub const name = " file type";
|
pub const name = " file type";
|
||||||
pub const description = "file type";
|
pub const description = "file type";
|
||||||
|
|
||||||
pub const Entry = struct {
|
pub const Entry = struct {
|
||||||
label: []const u8,
|
label: []const u8,
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
icon: []const u8,
|
icon: []const u8,
|
||||||
color: u24,
|
color: u24,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Match = struct {
|
pub const Match = struct {
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
score: i32,
|
score: i32,
|
||||||
matches: []const usize,
|
matches: []const usize,
|
||||||
};
|
};
|
||||||
|
|
||||||
var previous_file_type: ?[]const u8 = null;
|
var previous_file_type: ?[]const u8 = null;
|
||||||
|
|
||||||
pub fn load_entries(palette: *Type) !usize {
|
pub fn load_entries(palette: *Type) !usize {
|
||||||
var longest_hint: usize = 0;
|
var longest_hint: usize = 0;
|
||||||
var idx: usize = 0;
|
var idx: usize = 0;
|
||||||
previous_file_type = blk: {
|
previous_file_type = blk: {
|
||||||
if (tui.get_active_editor()) |editor|
|
if (tui.get_active_editor()) |editor|
|
||||||
if (editor.syntax) |editor_syntax|
|
if (editor.file_type) |editor_file_type|
|
||||||
break :blk editor_syntax.file_type.name;
|
break :blk editor_file_type.name;
|
||||||
break :blk null;
|
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);
|
var conf, const conf_bufs = root.read_config(@import("config"), allocator);
|
||||||
defer root.free_config(allocator, conf_bufs);
|
defer root.free_config(allocator, conf_bufs);
|
||||||
|
|
||||||
if (conf.start_debugger_on_crash)
|
if (@hasDecl(renderer, "install_crash_handler") and conf.start_debugger_on_crash)
|
||||||
tp.install_debugger();
|
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;
|
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;
|
conf.theme = theme_.name;
|
||||||
|
@ -917,11 +917,6 @@ const cmds = struct {
|
||||||
}
|
}
|
||||||
pub const change_theme_meta: Meta = .{ .description = "Change color theme" };
|
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 {
|
pub fn change_fontface(self: *Self, _: Ctx) Result {
|
||||||
if (build_options.gui)
|
if (build_options.gui)
|
||||||
self.rdr_.get_fontfaces();
|
self.rdr_.get_fontfaces();
|
||||||
|
@ -1117,6 +1112,10 @@ pub fn mini_mode() ?*MiniMode {
|
||||||
return if (current().mini_mode_) |*p| p else null;
|
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 {
|
pub fn query_cache() *syntax.QueryCache {
|
||||||
return current().query_cache_;
|
return current().query_cache_;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue