diff --git a/src/buffer/Buffer.zig b/src/buffer/Buffer.zig index e43ce96..12c1cbc 100644 --- a/src/buffer/Buffer.zig +++ b/src/buffer/Buffer.zig @@ -13,6 +13,7 @@ const Self = @This(); const max_imbalance = 7; pub const Root = *const Node; pub const unicode = @import("unicode.zig"); +pub const reflow = @import("reflow.zig").reflow; pub const Manager = @import("Manager.zig"); pub const Cursor = @import("Cursor.zig"); diff --git a/src/buffer/reflow.zig b/src/buffer/reflow.zig new file mode 100644 index 0000000..ebafa59 --- /dev/null +++ b/src/buffer/reflow.zig @@ -0,0 +1,55 @@ +pub fn reflow(allocator: std.mem.Allocator, text: []const u8, width: usize) error{ OutOfMemory, WriteFailed }![]u8 { + const prefix = detect_prefix(text); + const words = try split_words(allocator, text, prefix.len); + defer allocator.free(words); + var output: std.Io.Writer.Allocating = .init(allocator); + const writer = &output.writer; + + var line_len: usize = 0; + for (words) |word| { + if (line_len == 0) { + try writer.writeAll(prefix); + line_len += prefix.len; + } + if (line_len > prefix.len) + try writer.writeByte(' '); + try writer.writeAll(word); + line_len += word.len; + if (line_len >= width) { + try writer.writeByte('\n'); + line_len = 0; + } + } + + return output.toOwnedSlice(); +} + +fn split_words(allocator: std.mem.Allocator, text: []const u8, prefix: usize) error{OutOfMemory}![]const []const u8 { + var words: std.ArrayList([]const u8) = .empty; + var lines = std.mem.splitScalar(u8, text, '\n'); + while (lines.next()) |line| { + var it = std.mem.splitAny(u8, line[prefix..], " \t"); + while (it.next()) |word| if (word.len > 0) { + (try words.addOne(allocator)).* = word; + }; + } + return words.toOwnedSlice(allocator); +} + +fn detect_prefix(text: []const u8) []const u8 { + var lines = std.mem.splitScalar(u8, text, '\n'); + const line1 = lines.next() orelse return &.{}; + var prefix: []const u8 = line1; + while (lines.next()) |line| + prefix = lcp(prefix, line); + return prefix; +} + +fn lcp(a: []const u8, b: []const u8) []const u8 { + const len = @min(a.len, b.len); + for (0..len) |i| if (a[i] != b[i]) + return a[0..i]; + return a[0..len]; +} + +const std = @import("std"); diff --git a/src/config.zig b/src/config.zig index df91328..9ce45cc 100644 --- a/src/config.zig +++ b/src/config.zig @@ -45,6 +45,7 @@ auto_run_commands: ?[]const []const u8 = &.{"save_session_quiet"}, // a list of indent_size: usize = 4, tab_width: usize = 8, indent_mode: IndentMode = .auto, +reflow_width: usize = 80, top_bar: []const u8 = "tabs", bottom_bar: []const u8 = "mode file log selection diagnostics keybind branch linenumber clock spacer", diff --git a/src/keybind/builtin/flow.json b/src/keybind/builtin/flow.json index 4e36a8e..79d5ecb 100644 --- a/src/keybind/builtin/flow.json +++ b/src/keybind/builtin/flow.json @@ -143,6 +143,7 @@ ["alt+p", "goto_prev_file_or_diagnostic"], ["alt+u", "to_upper"], ["alt+l", "to_lower"], + ["alt+q", "reflow"], ["alt+c", "switch_case"], ["ctrl+_", "underline"], ["ctrl+=", "underline_with_char", "=", "solid"], diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 809828d..411d80f 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -6818,6 +6818,30 @@ pub const Editor = struct { self.filter_ = null; } + fn reflow_cursel(self: *Self, root_: Buffer.Root, cursel: *CurSel, allocator: Allocator) error{Stop}!Buffer.Root { + var root = root_; + var sel = cursel.selection orelse return root; + sel.normalize(); + var sfa = std.heap.stackFallback(4096, self.allocator); + const sfa_allocator = sfa.get(); + const cut_text = copy_selection(root, sel, sfa_allocator, self.metrics) catch return error.Stop; + defer sfa_allocator.free(cut_text); + const reflowed = Buffer.reflow(sfa_allocator, cut_text, tui.config().reflow_width) catch return error.Stop; + defer sfa_allocator.free(reflowed); + root = try self.delete_selection(root, cursel, allocator); + root = self.insert(root, cursel, reflowed, allocator) catch return error.Stop; + cursel.selection = .{ .begin = sel.begin, .end = cursel.cursor }; + return root; + } + + pub fn reflow(self: *Self, _: Context) Result { + const b = try self.buf_for_update(); + const root = try self.with_cursels_mut_once(b.root, reflow_cursel, b.allocator); + try self.update_buf(root); + self.clamp(); + } + pub const reflow_meta: Meta = .{ .description = "Reflow selection" }; + fn to_upper_cursel(self: *Self, root_: Buffer.Root, cursel: *CurSel, allocator: Allocator) error{Stop}!Buffer.Root { var root = root_; const saved = cursel.*;