Merge branch 'master' into wio-sokol-gui
This commit is contained in:
commit
b2b812431c
10 changed files with 280 additions and 12 deletions
|
|
@ -52,6 +52,7 @@ file_exists: bool = true,
|
||||||
file_eol_mode: EolMode = .lf,
|
file_eol_mode: EolMode = .lf,
|
||||||
last_save_eol_mode: EolMode = .lf,
|
last_save_eol_mode: EolMode = .lf,
|
||||||
file_utf8_sanitized: bool = false,
|
file_utf8_sanitized: bool = false,
|
||||||
|
detected_indent_size: ?usize = null,
|
||||||
hidden: bool = false,
|
hidden: bool = false,
|
||||||
ephemeral: bool = false,
|
ephemeral: bool = false,
|
||||||
auto_save: bool = false,
|
auto_save: bool = false,
|
||||||
|
|
@ -1395,9 +1396,47 @@ pub fn load(self: *const Self, reader: *std.Io.Reader, eol_mode: *EolMode, utf8_
|
||||||
leaves[cur_leaf] = .{ .leaf = .{ .buf = line, .bol = true, .eol = false } };
|
leaves[cur_leaf] = .{ .leaf = .{ .buf = line, .bol = true, .eol = false } };
|
||||||
if (leaves.len != cur_leaf + 1)
|
if (leaves.len != cur_leaf + 1)
|
||||||
return error.Unexpected;
|
return error.Unexpected;
|
||||||
|
|
||||||
|
self_.detected_indent_size = detect_indent_size(leaves[0..@min(leaves.len, 1000)]);
|
||||||
|
|
||||||
return Node.merge_in_place(leaves, self.allocator);
|
return Node.merge_in_place(leaves, self.allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn detect_indent_size(leaves: []const Node) ?usize {
|
||||||
|
// frequency of each leading-space count (up to 16 spaces).
|
||||||
|
const max_spaces = 16;
|
||||||
|
var freq = std.mem.zeroes([max_spaces + 1]u32);
|
||||||
|
for (leaves) |leaf_node| {
|
||||||
|
const line = leaf_node.leaf.buf;
|
||||||
|
if (line.len == 0) continue;
|
||||||
|
if (line[0] == '\t') return 0;
|
||||||
|
var spaces: usize = 0;
|
||||||
|
for (line) |c| {
|
||||||
|
if (c == ' ') spaces += 1 else break;
|
||||||
|
}
|
||||||
|
if (spaces == 0 or spaces > max_spaces) continue;
|
||||||
|
freq[spaces] += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find the 3 most frequently occurring indent levels
|
||||||
|
var top = [3]usize{ 0, 0, 0 };
|
||||||
|
for (1..freq.len) |n| {
|
||||||
|
if (freq[n] > freq[top[2]]) {
|
||||||
|
top[2] = n;
|
||||||
|
if (freq[top[2]] > freq[top[1]]) std.mem.swap(usize, &top[1], &top[2]);
|
||||||
|
if (freq[top[1]] > freq[top[0]]) std.mem.swap(usize, &top[0], &top[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GCD of the top indent levels gives the base indent unit
|
||||||
|
var gcd: usize = 0;
|
||||||
|
for (top) |n| {
|
||||||
|
if (n == 0) continue;
|
||||||
|
gcd = if (gcd == 0) n else std.math.gcd(gcd, n);
|
||||||
|
}
|
||||||
|
return if (gcd > 0) gcd else null;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn load_from_string(self: *const Self, s: []const u8, eol_mode: *EolMode, utf8_sanitized: *bool) LoadError!Root {
|
pub fn load_from_string(self: *const Self, s: []const u8, eol_mode: *EolMode, utf8_sanitized: *bool) LoadError!Root {
|
||||||
var reader = std.Io.Reader.fixed(s);
|
var reader = std.Io.Reader.fixed(s);
|
||||||
return self.load(&reader, eol_mode, utf8_sanitized);
|
return self.load(&reader, eol_mode, utf8_sanitized);
|
||||||
|
|
@ -1610,14 +1649,37 @@ pub fn store_to_existing_file_const(self: *const Self, file_path_: []const u8) S
|
||||||
file_path = link;
|
file_path = link;
|
||||||
}
|
}
|
||||||
|
|
||||||
var atomic = blk: {
|
var write_buffer: [4096]u8 = undefined;
|
||||||
var write_buffer: [4096]u8 = undefined;
|
|
||||||
const stat = cwd().statFile(file_path) catch
|
if (builtin.os.tag == .windows) {
|
||||||
break :blk try cwd().atomicFile(file_path, .{ .write_buffer = &write_buffer });
|
// windows uses ACLs for ownership so we preserve mode only
|
||||||
break :blk try cwd().atomicFile(file_path, .{ .mode = stat.mode, .write_buffer = &write_buffer });
|
var atomic = blk: {
|
||||||
|
const stat = cwd().statFile(file_path) catch
|
||||||
|
break :blk try cwd().atomicFile(file_path, .{ .write_buffer = &write_buffer });
|
||||||
|
break :blk try cwd().atomicFile(file_path, .{ .mode = stat.mode, .write_buffer = &write_buffer });
|
||||||
|
};
|
||||||
|
defer atomic.deinit();
|
||||||
|
try self.store_to_file_const(&atomic.file_writer.interface);
|
||||||
|
return atomic.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
// use fstat to get uid/gid, which std.fs.File.Stat omits.
|
||||||
|
const orig_stat: ?std.posix.Stat = blk: {
|
||||||
|
const f = cwd().openFile(file_path, .{}) catch break :blk null;
|
||||||
|
defer f.close();
|
||||||
|
break :blk std.posix.fstat(f.handle) catch null;
|
||||||
};
|
};
|
||||||
|
const mode: std.fs.File.Mode = if (orig_stat) |s| s.mode else std.fs.File.default_mode;
|
||||||
|
var atomic = try cwd().atomicFile(file_path, .{ .mode = mode, .write_buffer = &write_buffer });
|
||||||
defer atomic.deinit();
|
defer atomic.deinit();
|
||||||
try self.store_to_file_const(&atomic.file_writer.interface);
|
try self.store_to_file_const(&atomic.file_writer.interface);
|
||||||
|
// fchmod bypasses the process umask preserving the exact original mode
|
||||||
|
// fchown restores original owner/group
|
||||||
|
// EPERM is silently ignored when we lack sufficient privileges
|
||||||
|
if (orig_stat) |s| {
|
||||||
|
atomic.file_writer.file.chmod(s.mode) catch {};
|
||||||
|
std.posix.fchown(atomic.file_writer.file.handle, s.uid, s.gid) catch {};
|
||||||
|
}
|
||||||
try atomic.finish();
|
try atomic.finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ pub const char_pairs = [_]struct { []const u8, []const u8 }{
|
||||||
.{ "`", "`" },
|
.{ "`", "`" },
|
||||||
.{ "(", ")" },
|
.{ "(", ")" },
|
||||||
.{ "[", "]" },
|
.{ "[", "]" },
|
||||||
|
.{ "<", ">" },
|
||||||
.{ "{", "}" },
|
.{ "{", "}" },
|
||||||
.{ "‘", "’" },
|
.{ "‘", "’" },
|
||||||
.{ "“", "”" },
|
.{ "“", "”" },
|
||||||
|
|
@ -56,6 +57,7 @@ pub const char_pairs = [_]struct { []const u8, []const u8 }{
|
||||||
pub const open_close_pairs = [_]struct { []const u8, []const u8 }{
|
pub const open_close_pairs = [_]struct { []const u8, []const u8 }{
|
||||||
.{ "(", ")" },
|
.{ "(", ")" },
|
||||||
.{ "[", "]" },
|
.{ "[", "]" },
|
||||||
|
.{ "<", ">" },
|
||||||
.{ "{", "}" },
|
.{ "{", "}" },
|
||||||
.{ "‘", "’" },
|
.{ "‘", "’" },
|
||||||
.{ "“", "”" },
|
.{ "“", "”" },
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ input_mode: []const u8 = "flow",
|
||||||
gutter_line_numbers_mode: ?LineNumberMode = null,
|
gutter_line_numbers_mode: ?LineNumberMode = null,
|
||||||
gutter_line_numbers_style: DigitStyle = .ascii,
|
gutter_line_numbers_style: DigitStyle = .ascii,
|
||||||
gutter_symbols: bool = true,
|
gutter_symbols: bool = true,
|
||||||
|
gutter_diffs: bool = true,
|
||||||
gutter_width_mode: GutterWidthMode = .local,
|
gutter_width_mode: GutterWidthMode = .local,
|
||||||
gutter_width_minimum: usize = 4,
|
gutter_width_minimum: usize = 4,
|
||||||
gutter_width_maximum: usize = 8,
|
gutter_width_maximum: usize = 8,
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,8 @@
|
||||||
["di)", "cut_inside_parentheses"],
|
["di)", "cut_inside_parentheses"],
|
||||||
["di[", "cut_inside_square_brackets"],
|
["di[", "cut_inside_square_brackets"],
|
||||||
["di]", "cut_inside_square_brackets"],
|
["di]", "cut_inside_square_brackets"],
|
||||||
|
["di<LT>", "cut_inside_angle_brackets"],
|
||||||
|
["di<GT>", "cut_inside_angle_brackets"],
|
||||||
["di{", "cut_inside_braces"],
|
["di{", "cut_inside_braces"],
|
||||||
["di}", "cut_inside_braces"],
|
["di}", "cut_inside_braces"],
|
||||||
["di'", "cut_inside_single_quotes"],
|
["di'", "cut_inside_single_quotes"],
|
||||||
|
|
@ -96,6 +98,8 @@
|
||||||
["da)", "cut_around_parentheses"],
|
["da)", "cut_around_parentheses"],
|
||||||
["da[", "cut_around_square_brackets"],
|
["da[", "cut_around_square_brackets"],
|
||||||
["da]", "cut_around_square_brackets"],
|
["da]", "cut_around_square_brackets"],
|
||||||
|
["da<LT>", "cut_around_angle_brackets"],
|
||||||
|
["da<GT>", "cut_around_angle_brackets"],
|
||||||
["da{", "cut_around_braces"],
|
["da{", "cut_around_braces"],
|
||||||
["da}", "cut_around_braces"],
|
["da}", "cut_around_braces"],
|
||||||
["da'", "cut_around_single_quotes"],
|
["da'", "cut_around_single_quotes"],
|
||||||
|
|
@ -112,6 +116,8 @@
|
||||||
["ci)", ["enter_mode", "insert"], ["cut_inside_parentheses"]],
|
["ci)", ["enter_mode", "insert"], ["cut_inside_parentheses"]],
|
||||||
["ci[", ["enter_mode", "insert"], ["cut_inside_square_brackets"]],
|
["ci[", ["enter_mode", "insert"], ["cut_inside_square_brackets"]],
|
||||||
["ci]", ["enter_mode", "insert"], ["cut_inside_square_brackets"]],
|
["ci]", ["enter_mode", "insert"], ["cut_inside_square_brackets"]],
|
||||||
|
["ci<LT>", ["enter_mode", "insert"], ["cut_inside_angle_brackets"]],
|
||||||
|
["ci<GT>", ["enter_mode", "insert"], ["cut_inside_angle_brackets"]],
|
||||||
["ci{", ["enter_mode", "insert"], ["cut_inside_braces"]],
|
["ci{", ["enter_mode", "insert"], ["cut_inside_braces"]],
|
||||||
["ci}", ["enter_mode", "insert"], ["cut_inside_braces"]],
|
["ci}", ["enter_mode", "insert"], ["cut_inside_braces"]],
|
||||||
["ci'", ["enter_mode", "insert"], ["cut_inside_single_quotes"]],
|
["ci'", ["enter_mode", "insert"], ["cut_inside_single_quotes"]],
|
||||||
|
|
@ -122,6 +128,8 @@
|
||||||
["ca)", ["enter_mode", "insert"], ["cut_around_parentheses"]],
|
["ca)", ["enter_mode", "insert"], ["cut_around_parentheses"]],
|
||||||
["ca[", ["enter_mode", "insert"], ["cut_around_square_brackets"]],
|
["ca[", ["enter_mode", "insert"], ["cut_around_square_brackets"]],
|
||||||
["ca]", ["enter_mode", "insert"], ["cut_around_square_brackets"]],
|
["ca]", ["enter_mode", "insert"], ["cut_around_square_brackets"]],
|
||||||
|
["ca<LT>", ["enter_mode", "insert"], ["cut_around_angle_brackets"]],
|
||||||
|
["ca<GT>", ["enter_mode", "insert"], ["cut_around_angle_brackets"]],
|
||||||
["ca{", ["enter_mode", "insert"], ["cut_around_braces"]],
|
["ca{", ["enter_mode", "insert"], ["cut_around_braces"]],
|
||||||
["ca}", ["enter_mode", "insert"], ["cut_around_braces"]],
|
["ca}", ["enter_mode", "insert"], ["cut_around_braces"]],
|
||||||
["ca'", ["enter_mode", "insert"], ["cut_around_single_quotes"]],
|
["ca'", ["enter_mode", "insert"], ["cut_around_single_quotes"]],
|
||||||
|
|
@ -134,6 +142,8 @@
|
||||||
["yi)", ["copy_inside_parentheses"], ["cancel"]],
|
["yi)", ["copy_inside_parentheses"], ["cancel"]],
|
||||||
["yi[", ["copy_inside_square_brackets"], ["cancel"]],
|
["yi[", ["copy_inside_square_brackets"], ["cancel"]],
|
||||||
["yi]", ["copy_inside_square_brackets"], ["cancel"]],
|
["yi]", ["copy_inside_square_brackets"], ["cancel"]],
|
||||||
|
["yi<LT>", ["copy_inside_angle_brackets"], ["cancel"]],
|
||||||
|
["yi<GT>", ["copy_inside_angle_brackets"], ["cancel"]],
|
||||||
["yi{", ["copy_inside_braces"], ["cancel"]],
|
["yi{", ["copy_inside_braces"], ["cancel"]],
|
||||||
["yi}", ["copy_inside_braces"], ["cancel"]],
|
["yi}", ["copy_inside_braces"], ["cancel"]],
|
||||||
["yi'", ["copy_inside_single_quotes"], ["cancel"]],
|
["yi'", ["copy_inside_single_quotes"], ["cancel"]],
|
||||||
|
|
@ -144,6 +154,8 @@
|
||||||
["ya)", ["copy_around_parentheses"], ["cancel"]],
|
["ya)", ["copy_around_parentheses"], ["cancel"]],
|
||||||
["ya[", ["copy_around_square_brackets"], ["cancel"]],
|
["ya[", ["copy_around_square_brackets"], ["cancel"]],
|
||||||
["ya]", ["copy_around_square_brackets"], ["cancel"]],
|
["ya]", ["copy_around_square_brackets"], ["cancel"]],
|
||||||
|
["ya<LT>", ["copy_around_angle_brackets"], ["cancel"]],
|
||||||
|
["ya<GT>", ["copy_around_angle_brackets"], ["cancel"]],
|
||||||
["ya{", ["copy_around_braces"], ["cancel"]],
|
["ya{", ["copy_around_braces"], ["cancel"]],
|
||||||
["ya}", ["copy_around_braces"], ["cancel"]],
|
["ya}", ["copy_around_braces"], ["cancel"]],
|
||||||
["ya'", ["copy_around_single_quotes"], ["cancel"]],
|
["ya'", ["copy_around_single_quotes"], ["cancel"]],
|
||||||
|
|
@ -224,6 +236,8 @@
|
||||||
["i)", "select_inside_parentheses"],
|
["i)", "select_inside_parentheses"],
|
||||||
["i[", "select_inside_square_brackets"],
|
["i[", "select_inside_square_brackets"],
|
||||||
["i]", "select_inside_square_brackets"],
|
["i]", "select_inside_square_brackets"],
|
||||||
|
["i<LT>", "select_inside_angle_brackets"],
|
||||||
|
["i<GT>", "select_inside_angle_brackets"],
|
||||||
["i{", "select_inside_braces"],
|
["i{", "select_inside_braces"],
|
||||||
["i}", "select_inside_braces"],
|
["i}", "select_inside_braces"],
|
||||||
["i'", "select_inside_single_quotes"],
|
["i'", "select_inside_single_quotes"],
|
||||||
|
|
@ -234,6 +248,8 @@
|
||||||
["a)", "select_around_parentheses"],
|
["a)", "select_around_parentheses"],
|
||||||
["a[", "select_around_square_brackets"],
|
["a[", "select_around_square_brackets"],
|
||||||
["a]", "select_around_square_brackets"],
|
["a]", "select_around_square_brackets"],
|
||||||
|
["a<LT>", "select_around_angle_brackets"],
|
||||||
|
["a<GT>", "select_around_angle_brackets"],
|
||||||
["a{", "select_around_braces"],
|
["a{", "select_around_braces"],
|
||||||
["a}", "select_around_braces"],
|
["a}", "select_around_braces"],
|
||||||
["a'", "select_around_single_quotes"],
|
["a'", "select_around_single_quotes"],
|
||||||
|
|
|
||||||
|
|
@ -831,6 +831,13 @@ pub const Editor = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn detect_indent_mode(self: *Self, content: []const u8) void {
|
fn detect_indent_mode(self: *Self, content: []const u8) void {
|
||||||
|
if (self.buffer) |buf| {
|
||||||
|
if (buf.detected_indent_size) |detected_indent_size| {
|
||||||
|
self.indent_size = detected_indent_size;
|
||||||
|
self.indent_mode = .spaces;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
var it = std.mem.splitScalar(u8, content, '\n');
|
var it = std.mem.splitScalar(u8, content, '\n');
|
||||||
while (it.next()) |line| {
|
while (it.next()) |line| {
|
||||||
if (line.len == 0) continue;
|
if (line.len == 0) continue;
|
||||||
|
|
@ -842,7 +849,6 @@ pub const Editor = struct {
|
||||||
}
|
}
|
||||||
self.indent_size = tui.config().indent_size;
|
self.indent_size = tui.config().indent_size;
|
||||||
self.indent_mode = .spaces;
|
self.indent_mode = .spaces;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn refresh_tab_width(self: *Self) void {
|
fn refresh_tab_width(self: *Self) void {
|
||||||
|
|
@ -4232,11 +4238,25 @@ pub const Editor = struct {
|
||||||
fn pull_cursel_up(self: *Self, root_: Buffer.Root, cursel: *CurSel, allocator: Allocator) error{Stop}!Buffer.Root {
|
fn pull_cursel_up(self: *Self, root_: Buffer.Root, cursel: *CurSel, allocator: Allocator) error{Stop}!Buffer.Root {
|
||||||
var root = root_;
|
var root = root_;
|
||||||
const saved = cursel.*;
|
const saved = cursel.*;
|
||||||
|
errdefer cursel.* = saved;
|
||||||
const sel = cursel.expand_selection_to_line(root, self.metrics);
|
const sel = cursel.expand_selection_to_line(root, self.metrics);
|
||||||
var sfa = std.heap.stackFallback(4096, self.allocator);
|
var sfa = std.heap.stackFallback(4096, self.allocator);
|
||||||
const sfa_allocator = sfa.get();
|
const sfa_allocator = sfa.get();
|
||||||
const cut_text = copy_selection(root, sel.*, sfa_allocator, self.metrics) catch return error.Stop;
|
const cut_text_raw = copy_selection(root, sel.*, sfa_allocator, self.metrics) catch return error.Stop;
|
||||||
defer sfa_allocator.free(cut_text);
|
defer sfa_allocator.free(cut_text_raw);
|
||||||
|
|
||||||
|
const is_last_no_nl = sel.end.row == cursel.cursor.row;
|
||||||
|
|
||||||
|
var cut_text_buf: ?[]u8 = null;
|
||||||
|
defer if (cut_text_buf) |t| sfa_allocator.free(t);
|
||||||
|
const cut_text: []const u8 = if (is_last_no_nl) blk: {
|
||||||
|
const buf = sfa_allocator.alloc(u8, cut_text_raw.len + 1) catch return error.Stop;
|
||||||
|
cut_text_buf = buf;
|
||||||
|
@memcpy(buf[0..cut_text_raw.len], cut_text_raw);
|
||||||
|
buf[cut_text_raw.len] = '\n';
|
||||||
|
break :blk buf;
|
||||||
|
} else cut_text_raw;
|
||||||
|
|
||||||
root = try self.delete_selection(root, cursel, allocator);
|
root = try self.delete_selection(root, cursel, allocator);
|
||||||
try cursel.cursor.move_up(root, self.metrics);
|
try cursel.cursor.move_up(root, self.metrics);
|
||||||
root = self.insert(root, cursel, cut_text, allocator) catch return error.Stop;
|
root = self.insert(root, cursel, cut_text, allocator) catch return error.Stop;
|
||||||
|
|
@ -4246,6 +4266,17 @@ pub const Editor = struct {
|
||||||
try sel_.begin.move_up(root, self.metrics);
|
try sel_.begin.move_up(root, self.metrics);
|
||||||
try sel_.end.move_up(root, self.metrics);
|
try sel_.end.move_up(root, self.metrics);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (is_last_no_nl) {
|
||||||
|
const last_content_row = root.lines() - 2;
|
||||||
|
var del_begin: Cursor = .{ .row = last_content_row, .col = 0 };
|
||||||
|
del_begin.move_end(root, self.metrics);
|
||||||
|
var tmp: CurSel = .{
|
||||||
|
.cursor = del_begin,
|
||||||
|
.selection = .{ .begin = del_begin, .end = .{ .row = last_content_row + 1, .col = 0 } },
|
||||||
|
};
|
||||||
|
root = try self.delete_selection(root, &tmp, allocator);
|
||||||
|
}
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -4260,14 +4291,35 @@ pub const Editor = struct {
|
||||||
fn pull_cursel_down(self: *Self, root_: Buffer.Root, cursel: *CurSel, allocator: Allocator) error{Stop}!Buffer.Root {
|
fn pull_cursel_down(self: *Self, root_: Buffer.Root, cursel: *CurSel, allocator: Allocator) error{Stop}!Buffer.Root {
|
||||||
var root = root_;
|
var root = root_;
|
||||||
const saved = cursel.*;
|
const saved = cursel.*;
|
||||||
|
errdefer cursel.* = saved;
|
||||||
|
const cursor_row_before = cursel.cursor.row;
|
||||||
|
const lines_before = root.lines();
|
||||||
const sel = cursel.expand_selection_to_line(root, self.metrics);
|
const sel = cursel.expand_selection_to_line(root, self.metrics);
|
||||||
var sfa = std.heap.stackFallback(4096, self.allocator);
|
var sfa = std.heap.stackFallback(4096, self.allocator);
|
||||||
const sfa_allocator = sfa.get();
|
const sfa_allocator = sfa.get();
|
||||||
const cut_text = copy_selection(root, sel.*, sfa_allocator, self.metrics) catch return error.Stop;
|
const cut_text = if (sel.empty())
|
||||||
|
&.{}
|
||||||
|
else
|
||||||
|
copy_selection(root, sel.*, sfa_allocator, self.metrics) catch return error.Stop;
|
||||||
defer sfa_allocator.free(cut_text);
|
defer sfa_allocator.free(cut_text);
|
||||||
root = try self.delete_selection(root, cursel, allocator);
|
root = try self.delete_selection(root, cursel, allocator);
|
||||||
try cursel.cursor.move_down(root, self.metrics);
|
const moved_down = blk: {
|
||||||
root = self.insert(root, cursel, cut_text, allocator) catch return error.Stop;
|
cursel.cursor.move_down(root, self.metrics) catch break :blk false;
|
||||||
|
break :blk true;
|
||||||
|
};
|
||||||
|
if (moved_down) {
|
||||||
|
root = self.insert(root, cursel, cut_text, allocator) catch return error.Stop;
|
||||||
|
} else {
|
||||||
|
if (cursor_row_before >= lines_before - 1) return error.Stop;
|
||||||
|
cursel.cursor.move_end(root, self.metrics);
|
||||||
|
root = self.insert(root, cursel, "\n", allocator) catch return error.Stop;
|
||||||
|
const cut_no_nl = if (std.mem.endsWith(u8, cut_text, "\n"))
|
||||||
|
cut_text[0 .. cut_text.len - 1]
|
||||||
|
else
|
||||||
|
cut_text;
|
||||||
|
if (cut_no_nl.len > 0)
|
||||||
|
root = self.insert(root, cursel, cut_no_nl, allocator) catch return error.Stop;
|
||||||
|
}
|
||||||
cursel.* = saved;
|
cursel.* = saved;
|
||||||
try cursel.cursor.move_down(root, self.metrics);
|
try cursel.cursor.move_down(root, self.metrics);
|
||||||
if (cursel.selection) |*sel_| {
|
if (cursel.selection) |*sel_| {
|
||||||
|
|
|
||||||
|
|
@ -262,6 +262,7 @@ inline fn render_line_highlight(self: *Self, pos: usize, theme: *const Widget.Th
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fn render_diff_symbols(self: *Self, diff_symbols: *[]Diff, pos: usize, linenum_: usize, theme: *const Widget.Theme) void {
|
inline fn render_diff_symbols(self: *Self, diff_symbols: *[]Diff, pos: usize, linenum_: usize, theme: *const Widget.Theme) void {
|
||||||
|
if (!tui.config().gutter_diffs) return;
|
||||||
const linenum = linenum_ - 1;
|
const linenum = linenum_ - 1;
|
||||||
if (diff_symbols.len == 0) return;
|
if (diff_symbols.len == 0) return;
|
||||||
while ((diff_symbols.*)[0].line < linenum) {
|
while ((diff_symbols.*)[0].line < linenum) {
|
||||||
|
|
|
||||||
|
|
@ -197,7 +197,34 @@ fn handle_render_menu(self: *Self, button: *ButtonType, theme: *const Widget.The
|
||||||
.Warning => button.plane.set_style(style_warning),
|
.Warning => button.plane.set_style(style_warning),
|
||||||
.Error => button.plane.set_style(style_error),
|
.Error => button.plane.set_style(style_error),
|
||||||
}
|
}
|
||||||
_ = button.plane.print("{f}", .{std.ascii.hexEscape(entry.lines, .lower)}) catch {};
|
const tab_width = tui.config().tab_width;
|
||||||
|
const show_tabs_visual = switch (tui.config().whitespace_mode) {
|
||||||
|
.tabs, .external, .visible, .full => true,
|
||||||
|
else => false,
|
||||||
|
};
|
||||||
|
var codepoints = (std.unicode.Utf8View.init(entry.lines) catch std.unicode.Utf8View.initUnchecked(entry.lines)).iterator();
|
||||||
|
while (codepoints.nextCodepointSlice()) |codepoint| {
|
||||||
|
const cp = std.unicode.utf8Decode(codepoint) catch {
|
||||||
|
for (codepoint) |b| _ = button.plane.print("\\x{x:0>2}", .{b}) catch {};
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
switch (cp) {
|
||||||
|
'\t' => {
|
||||||
|
const col: usize = @intCast(button.plane.cursor_x());
|
||||||
|
const spaces = tab_width - (col % tab_width);
|
||||||
|
if (show_tabs_visual) {
|
||||||
|
button.plane.set_style(.{ .fg = theme.editor_whitespace.fg, .bg = style_label.bg });
|
||||||
|
for (0..spaces) |i|
|
||||||
|
_ = button.plane.putstr(if (i < spaces - 1) editor.whitespace.char.tab_begin else editor.whitespace.char.tab_end) catch {};
|
||||||
|
button.plane.set_style(style_label);
|
||||||
|
} else {
|
||||||
|
for (0..spaces) |_| _ = button.plane.putstr(" ") catch {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
0x00...0x08, 0x0a...0x1f, 0x7f => _ = button.plane.print("\\x{x:0>2}", .{cp}) catch {},
|
||||||
|
else => _ = button.plane.putstr(codepoint) catch {},
|
||||||
|
}
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1165,6 +1165,13 @@ const cmds = struct {
|
||||||
}
|
}
|
||||||
pub const toggle_inline_diagnostics_meta: Meta = .{ .description = "Toggle display of diagnostics inline" };
|
pub const toggle_inline_diagnostics_meta: Meta = .{ .description = "Toggle display of diagnostics inline" };
|
||||||
|
|
||||||
|
pub fn toggle_gutter_diffs(_: *Self, _: Ctx) Result {
|
||||||
|
const config = tui.config_mut();
|
||||||
|
config.gutter_diffs = !config.gutter_diffs;
|
||||||
|
try tui.save_config();
|
||||||
|
}
|
||||||
|
pub const toggle_gutter_diffs_meta: Meta = .{ .description = "Toggle gutter diff markers" };
|
||||||
|
|
||||||
pub fn goto_next_file_or_diagnostic(self: *Self, ctx: Ctx) Result {
|
pub fn goto_next_file_or_diagnostic(self: *Self, ctx: Ctx) Result {
|
||||||
if (self.is_panel_view_showing(filelist_view)) {
|
if (self.is_panel_view_showing(filelist_view)) {
|
||||||
switch (self.file_list_type) {
|
switch (self.file_list_type) {
|
||||||
|
|
|
||||||
|
|
@ -202,6 +202,24 @@ const cmds_ = struct {
|
||||||
}
|
}
|
||||||
pub const select_around_square_brackets_meta: Meta = .{ .description = "Select around []" };
|
pub const select_around_square_brackets_meta: Meta = .{ .description = "Select around []" };
|
||||||
|
|
||||||
|
pub fn select_inside_angle_brackets(_: *void, _: Ctx) Result {
|
||||||
|
const mv = tui.mainview() orelse return;
|
||||||
|
const ed = mv.get_active_editor() orelse return;
|
||||||
|
const root = ed.buf_root() catch return;
|
||||||
|
|
||||||
|
try ed.with_cursels_const(root, select_inside_angle_brackets_textobject, ed.metrics);
|
||||||
|
}
|
||||||
|
pub const select_inside_angle_brackets_meta: Meta = .{ .description = "Select inside <>" };
|
||||||
|
|
||||||
|
pub fn select_around_angle_brackets(_: *void, _: Ctx) Result {
|
||||||
|
const mv = tui.mainview() orelse return;
|
||||||
|
const ed = mv.get_active_editor() orelse return;
|
||||||
|
const root = ed.buf_root() catch return;
|
||||||
|
|
||||||
|
try ed.with_cursels_const(root, select_around_angle_brackets_textobject, ed.metrics);
|
||||||
|
}
|
||||||
|
pub const select_around_angle_brackets_meta: Meta = .{ .description = "Select around <>" };
|
||||||
|
|
||||||
pub fn select_inside_braces(_: *void, _: Ctx) Result {
|
pub fn select_inside_braces(_: *void, _: Ctx) Result {
|
||||||
const mv = tui.mainview() orelse return;
|
const mv = tui.mainview() orelse return;
|
||||||
const ed = mv.get_active_editor() orelse return;
|
const ed = mv.get_active_editor() orelse return;
|
||||||
|
|
@ -316,6 +334,26 @@ const cmds_ = struct {
|
||||||
}
|
}
|
||||||
pub const cut_around_square_brackets_meta: Meta = .{ .description = "Cut around []" };
|
pub const cut_around_square_brackets_meta: Meta = .{ .description = "Cut around []" };
|
||||||
|
|
||||||
|
pub fn cut_inside_angle_brackets(_: *void, ctx: Ctx) Result {
|
||||||
|
const mv = tui.mainview() orelse return;
|
||||||
|
const ed = mv.get_active_editor() orelse return;
|
||||||
|
const root = ed.buf_root() catch return;
|
||||||
|
|
||||||
|
try ed.with_cursels_const(root, select_inside_angle_brackets_textobject, ed.metrics);
|
||||||
|
try ed.cut_internal_vim(ctx);
|
||||||
|
}
|
||||||
|
pub const cut_inside_angle_brackets_meta: Meta = .{ .description = "Cut inside <>" };
|
||||||
|
|
||||||
|
pub fn cut_around_angle_brackets(_: *void, ctx: Ctx) Result {
|
||||||
|
const mv = tui.mainview() orelse return;
|
||||||
|
const ed = mv.get_active_editor() orelse return;
|
||||||
|
const root = ed.buf_root() catch return;
|
||||||
|
|
||||||
|
try ed.with_cursels_const(root, select_around_angle_brackets_textobject, ed.metrics);
|
||||||
|
try ed.cut_internal_vim(ctx);
|
||||||
|
}
|
||||||
|
pub const cut_around_angle_brackets_meta: Meta = .{ .description = "Cut around <>" };
|
||||||
|
|
||||||
pub fn cut_inside_braces(_: *void, ctx: Ctx) Result {
|
pub fn cut_inside_braces(_: *void, ctx: Ctx) Result {
|
||||||
const mv = tui.mainview() orelse return;
|
const mv = tui.mainview() orelse return;
|
||||||
const ed = mv.get_active_editor() orelse return;
|
const ed = mv.get_active_editor() orelse return;
|
||||||
|
|
@ -436,6 +474,26 @@ const cmds_ = struct {
|
||||||
}
|
}
|
||||||
pub const copy_around_square_brackets_meta: Meta = .{ .description = "Copy around []" };
|
pub const copy_around_square_brackets_meta: Meta = .{ .description = "Copy around []" };
|
||||||
|
|
||||||
|
pub fn copy_inside_angle_brackets(_: *void, ctx: Ctx) Result {
|
||||||
|
const mv = tui.mainview() orelse return;
|
||||||
|
const ed = mv.get_active_editor() orelse return;
|
||||||
|
const root = ed.buf_root() catch return;
|
||||||
|
|
||||||
|
try ed.with_cursels_const(root, select_inside_angle_brackets_textobject, ed.metrics);
|
||||||
|
try ed.copy_internal_vim(ctx);
|
||||||
|
}
|
||||||
|
pub const copy_inside_angle_brackets_meta: Meta = .{ .description = "Copy inside <>" };
|
||||||
|
|
||||||
|
pub fn copy_around_angle_brackets(_: *void, ctx: Ctx) Result {
|
||||||
|
const mv = tui.mainview() orelse return;
|
||||||
|
const ed = mv.get_active_editor() orelse return;
|
||||||
|
const root = ed.buf_root() catch return;
|
||||||
|
|
||||||
|
try ed.with_cursels_const(root, select_around_angle_brackets_textobject, ed.metrics);
|
||||||
|
try ed.copy_internal_vim(ctx);
|
||||||
|
}
|
||||||
|
pub const copy_around_angle_brackets_meta: Meta = .{ .description = "Copy around <>" };
|
||||||
|
|
||||||
pub fn copy_inside_braces(_: *void, ctx: Ctx) Result {
|
pub fn copy_inside_braces(_: *void, ctx: Ctx) Result {
|
||||||
const mv = tui.mainview() orelse return;
|
const mv = tui.mainview() orelse return;
|
||||||
const ed = mv.get_active_editor() orelse return;
|
const ed = mv.get_active_editor() orelse return;
|
||||||
|
|
@ -575,6 +633,14 @@ fn select_around_square_brackets_textobject(root: Buffer.Root, cursel: *CurSel,
|
||||||
return try select_scope_textobject(root, cursel, metrics, "[", "]", .around);
|
return try select_scope_textobject(root, cursel, metrics, "[", "]", .around);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn select_inside_angle_brackets_textobject(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void {
|
||||||
|
return try select_scope_textobject(root, cursel, metrics, "<", ">", .inside);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_around_angle_brackets_textobject(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void {
|
||||||
|
return try select_scope_textobject(root, cursel, metrics, "<", ">", .around);
|
||||||
|
}
|
||||||
|
|
||||||
fn select_inside_braces_textobject(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void {
|
fn select_inside_braces_textobject(root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void {
|
||||||
return try select_scope_textobject(root, cursel, metrics, "{", "}", .inside);
|
return try select_scope_textobject(root, cursel, metrics, "{", "}", .inside);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
const Buffer = @import("Buffer");
|
const Buffer = @import("Buffer");
|
||||||
|
|
||||||
const ArrayList = std.ArrayList;
|
const ArrayList = std.ArrayList;
|
||||||
|
|
@ -99,6 +100,39 @@ test "buffer.store_to_file_and_clean" {
|
||||||
try std.testing.expectEqualStrings(input, output);
|
try std.testing.expectEqualStrings(input, output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "buffer.store_to_file_and_clean preserves file mode" {
|
||||||
|
if (comptime builtin.os.tag == .windows) return error.SkipZigTest;
|
||||||
|
|
||||||
|
const tmp_path = "test/tmp_mode_test.txt";
|
||||||
|
{
|
||||||
|
const f = try std.fs.cwd().createFile(tmp_path, .{});
|
||||||
|
defer f.close();
|
||||||
|
try f.writeAll("hello\n");
|
||||||
|
try f.chmod(0o644);
|
||||||
|
}
|
||||||
|
defer std.fs.cwd().deleteFile(tmp_path) catch {};
|
||||||
|
|
||||||
|
// Set a umask that would strip group/other read bits (0o644 -> 0o600 without the fix)
|
||||||
|
// to verify that fchmod bypasses the process umask on save.
|
||||||
|
const prev_umask = if (comptime builtin.os.tag == .linux)
|
||||||
|
std.os.linux.syscall1(.umask, 0o077)
|
||||||
|
else
|
||||||
|
@as(usize, 0);
|
||||||
|
defer if (comptime builtin.os.tag == .linux) {
|
||||||
|
_ = std.os.linux.syscall1(.umask, prev_umask);
|
||||||
|
};
|
||||||
|
|
||||||
|
const buffer = try Buffer.create(a);
|
||||||
|
defer buffer.deinit();
|
||||||
|
try buffer.load_from_file_and_update(tmp_path);
|
||||||
|
try buffer.store_to_file_and_clean(tmp_path);
|
||||||
|
|
||||||
|
const f = try std.fs.cwd().openFile(tmp_path, .{});
|
||||||
|
defer f.close();
|
||||||
|
const stat = try std.posix.fstat(f.handle);
|
||||||
|
try std.testing.expectEqual(@as(std.posix.mode_t, 0o644), stat.mode & 0o777);
|
||||||
|
}
|
||||||
|
|
||||||
fn get_line(buf: *const Buffer, line: usize) ![]const u8 {
|
fn get_line(buf: *const Buffer, line: usize) ![]const u8 {
|
||||||
var result: std.Io.Writer.Allocating = .init(a);
|
var result: std.Io.Writer.Allocating = .init(a);
|
||||||
try buf.root.get_line(line, &result.writer, metrics());
|
try buf.root.get_line(line, &result.writer, metrics());
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue