Compare commits
8 commits
933126e2a0
...
2461717f11
| Author | SHA1 | Date | |
|---|---|---|---|
| 2461717f11 | |||
| 7228a604b0 | |||
| 219b8cd00a | |||
| 7c5a22c959 | |||
| 30a457158c | |||
| 18cd62ba7e | |||
| 935b178d89 | |||
| 1658c9e3b4 |
9 changed files with 222 additions and 15 deletions
|
|
@ -794,6 +794,35 @@ const Node = union(enum) {
|
|||
return if (found) ctx.result else error.NotFound;
|
||||
}
|
||||
|
||||
pub fn byte_offset_to_line_and_col(self: *const Node, pos: usize, metrics: Metrics, eol_mode: EolMode) Cursor {
|
||||
const ctx_ = struct {
|
||||
pos: usize,
|
||||
line: usize = 0,
|
||||
col: usize = 0,
|
||||
eol_mode: EolMode,
|
||||
fn walker(ctx_: *anyopaque, egc: []const u8, wcwidth: usize, _: Metrics) Walker {
|
||||
const ctx = @as(*@This(), @ptrCast(@alignCast(ctx_)));
|
||||
if (egc[0] == '\n') {
|
||||
ctx.pos -= switch (ctx.eol_mode) {
|
||||
.lf => 1,
|
||||
.crlf => @min(2, ctx.pos),
|
||||
};
|
||||
if (ctx.pos == 0) return Walker.stop;
|
||||
ctx.line += 1;
|
||||
ctx.col = 0;
|
||||
} else {
|
||||
ctx.pos -= @min(egc.len, ctx.pos);
|
||||
if (ctx.pos == 0) return Walker.stop;
|
||||
ctx.col += wcwidth;
|
||||
}
|
||||
return Walker.keep_walking;
|
||||
}
|
||||
};
|
||||
var ctx: ctx_ = .{ .pos = pos + 1, .eol_mode = eol_mode };
|
||||
self.walk_egc_forward(0, ctx_.walker, &ctx, metrics) catch {};
|
||||
return .{ .row = ctx.line, .col = ctx.col };
|
||||
}
|
||||
|
||||
pub fn insert_chars(
|
||||
self_: *const Node,
|
||||
line_: usize,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ pub const FileDest = struct {
|
|||
column: ?usize = null,
|
||||
end_column: ?usize = null,
|
||||
exists: bool = false,
|
||||
offset: ?usize = null,
|
||||
};
|
||||
|
||||
pub const DirDest = struct {
|
||||
|
|
@ -37,11 +38,17 @@ pub fn parse(link: []const u8) error{InvalidFileLink}!Dest {
|
|||
.{ .file = .{ .path = it.first() } };
|
||||
switch (dest) {
|
||||
.file => |*file| {
|
||||
if (it.next()) |line_|
|
||||
if (it.next()) |line_| if (line_.len > 0 and line_[0] == 'b') {
|
||||
file.offset = std.fmt.parseInt(usize, line_[1..], 10) catch blk: {
|
||||
file.path = link;
|
||||
break :blk null;
|
||||
};
|
||||
} else {
|
||||
file.line = std.fmt.parseInt(usize, line_, 10) catch blk: {
|
||||
file.path = link;
|
||||
break :blk null;
|
||||
};
|
||||
};
|
||||
if (file.line) |_| if (it.next()) |col_| {
|
||||
file.column = std.fmt.parseInt(usize, col_, 10) catch null;
|
||||
};
|
||||
|
|
@ -88,6 +95,9 @@ pub fn parse_bracket_link(link: []const u8) error{InvalidFileLink}!Dest {
|
|||
pub fn navigate(to: tp.pid_ref, link: *const Dest) anyerror!void {
|
||||
switch (link.*) {
|
||||
.file => |file| {
|
||||
if (file.offset) |offset| {
|
||||
return to.send(.{ "cmd", "navigate", .{ .file = file.path, .offset = offset } });
|
||||
}
|
||||
if (file.line) |l| {
|
||||
if (file.column) |col| {
|
||||
try to.send(.{ "cmd", "navigate", .{ .file = file.path, .line = l, .column = col } });
|
||||
|
|
|
|||
|
|
@ -341,6 +341,7 @@
|
|||
},
|
||||
"mini/numeric": {
|
||||
"press": [
|
||||
["b", "goto_offset"],
|
||||
["ctrl+q", "quit"],
|
||||
["ctrl+v", "system_paste"],
|
||||
["ctrl+u", "mini_mode_reset"],
|
||||
|
|
|
|||
42
src/main.zig
42
src/main.zig
|
|
@ -245,19 +245,34 @@ pub fn main() anyerror!void {
|
|||
defer links.deinit();
|
||||
var prev: ?*file_link.Dest = null;
|
||||
var line_next: ?usize = null;
|
||||
var offset_next: ?usize = null;
|
||||
for (positional_args.items) |arg| {
|
||||
if (arg.len == 0) continue;
|
||||
|
||||
if (!args.literal and arg[0] == '+') {
|
||||
const line = try std.fmt.parseInt(usize, arg[1..], 10);
|
||||
if (prev) |p| switch (p.*) {
|
||||
.file => |*file| {
|
||||
file.line = line;
|
||||
continue;
|
||||
},
|
||||
else => {},
|
||||
};
|
||||
line_next = line;
|
||||
if (arg.len > 2 and arg[1] == 'b') {
|
||||
const offset = try std.fmt.parseInt(usize, arg[2..], 10);
|
||||
if (prev) |p| switch (p.*) {
|
||||
.file => |*file| {
|
||||
file.offset = offset;
|
||||
continue;
|
||||
},
|
||||
else => {},
|
||||
};
|
||||
offset_next = offset;
|
||||
line_next = null;
|
||||
} else {
|
||||
const line = try std.fmt.parseInt(usize, arg[1..], 10);
|
||||
if (prev) |p| switch (p.*) {
|
||||
.file => |*file| {
|
||||
file.line = line;
|
||||
continue;
|
||||
},
|
||||
else => {},
|
||||
};
|
||||
line_next = line;
|
||||
offset_next = null;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -274,6 +289,15 @@ pub fn main() anyerror!void {
|
|||
else => {},
|
||||
}
|
||||
}
|
||||
if (offset_next) |offset| {
|
||||
switch (curr.*) {
|
||||
.file => |*file| {
|
||||
file.offset = offset;
|
||||
offset_next = null;
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var have_project = false;
|
||||
|
|
|
|||
|
|
@ -5477,6 +5477,28 @@ pub const Editor = struct {
|
|||
}
|
||||
pub const goto_line_and_column_meta: Meta = .{ .arguments = &.{ .integer, .integer } };
|
||||
|
||||
pub fn goto_byte_offset(self: *Self, ctx: Context) Result {
|
||||
try self.send_editor_jump_source();
|
||||
var offset: usize = 0;
|
||||
if (try ctx.args.match(.{
|
||||
tp.extract(&offset),
|
||||
})) {
|
||||
// self.logger.print("goto: byte offset:{d}", .{ offset });
|
||||
} else return error.InvalidGotoByteOffsetArgument;
|
||||
self.cancel_all_selections();
|
||||
const root = self.buf_root() catch return;
|
||||
const eol_mode = self.buf_eol_mode() catch return;
|
||||
const primary = self.get_primary();
|
||||
primary.cursor = root.byte_offset_to_line_and_col(offset, self.metrics, eol_mode);
|
||||
if (self.view.is_visible(&primary.cursor))
|
||||
self.clamp()
|
||||
else
|
||||
try self.scroll_view_center(.{});
|
||||
try self.send_editor_jump_destination();
|
||||
self.need_render();
|
||||
}
|
||||
pub const goto_byte_offset_meta: Meta = .{ .arguments = &.{.integer} };
|
||||
|
||||
pub fn goto_definition(self: *Self, _: Context) Result {
|
||||
const file_path = self.file_path orelse return;
|
||||
const primary = self.get_primary();
|
||||
|
|
|
|||
|
|
@ -150,10 +150,10 @@ pub fn receive(self: *Self, from_: tp.pid_ref, m: tp.message) error{Exit}!bool {
|
|||
});
|
||||
return true;
|
||||
} else if (try m.match(.{ "navigate_complete", tp.extract(&same_file), tp.extract(&path), tp.extract(&goto_args), tp.extract(&line), tp.extract(&column) })) {
|
||||
cmds.navigate_complete(self, same_file, path, goto_args, line, column) catch |e| return tp.exit_error(e, @errorReturnTrace());
|
||||
cmds.navigate_complete(self, same_file, path, goto_args, line, column, null) catch |e| return tp.exit_error(e, @errorReturnTrace());
|
||||
return true;
|
||||
} else if (try m.match(.{ "navigate_complete", tp.extract(&same_file), tp.extract(&path), tp.extract(&goto_args), tp.null_, tp.null_ })) {
|
||||
cmds.navigate_complete(self, same_file, path, goto_args, null, null) catch |e| return tp.exit_error(e, @errorReturnTrace());
|
||||
cmds.navigate_complete(self, same_file, path, goto_args, null, null, null) catch |e| return tp.exit_error(e, @errorReturnTrace());
|
||||
return true;
|
||||
}
|
||||
return if (try self.floating_views.send(from_, m)) true else self.widgets.send(from_, m);
|
||||
|
|
@ -349,6 +349,7 @@ const cmds = struct {
|
|||
var file_name: []const u8 = undefined;
|
||||
var line: ?i64 = null;
|
||||
var column: ?i64 = null;
|
||||
var offset: ?i64 = null;
|
||||
var goto_args: []const u8 = &.{};
|
||||
|
||||
var iter = ctx.args.buf;
|
||||
|
|
@ -370,6 +371,9 @@ const cmds = struct {
|
|||
} else if (std.mem.eql(u8, field_name, "goto")) {
|
||||
if (!try cbor.matchValue(&iter, cbor.extract_cbor(&goto_args)))
|
||||
return error.InvalidNavigateGotoArgument;
|
||||
} else if (std.mem.eql(u8, field_name, "offset")) {
|
||||
if (!try cbor.matchValue(&iter, cbor.extract(&offset)))
|
||||
return error.InvalidNavigateOffsetArgument;
|
||||
} else {
|
||||
try cbor.skipValue(&iter);
|
||||
}
|
||||
|
|
@ -392,7 +396,8 @@ const cmds = struct {
|
|||
if (tui.config().restore_last_cursor_position and
|
||||
!same_file and
|
||||
!have_editor_metadata and
|
||||
line == null)
|
||||
line == null and
|
||||
offset == null)
|
||||
{
|
||||
const ctx_: struct {
|
||||
allocator: std.mem.Allocator,
|
||||
|
|
@ -424,11 +429,11 @@ const cmds = struct {
|
|||
return;
|
||||
}
|
||||
|
||||
return cmds.navigate_complete(self, same_file, f, goto_args, line, column);
|
||||
return cmds.navigate_complete(self, same_file, f, goto_args, line, column, offset);
|
||||
}
|
||||
pub const navigate_meta: Meta = .{ .arguments = &.{.object} };
|
||||
|
||||
fn navigate_complete(self: *Self, same_file: bool, f: []const u8, goto_args: []const u8, line: ?i64, column: ?i64) Result {
|
||||
fn navigate_complete(self: *Self, same_file: bool, f: []const u8, goto_args: []const u8, line: ?i64, column: ?i64, offset: ?i64) Result {
|
||||
if (!same_file) {
|
||||
if (self.get_active_editor()) |editor| {
|
||||
editor.send_editor_jump_source() catch {};
|
||||
|
|
@ -444,6 +449,10 @@ const cmds = struct {
|
|||
try command.executeName("scroll_view_center", .{});
|
||||
if (column) |col|
|
||||
try command.executeName("goto_column", command.fmt(.{col}));
|
||||
} else if (offset) |o| {
|
||||
try command.executeName("goto_byte_offset", command.fmt(.{o}));
|
||||
if (!same_file)
|
||||
try command.executeName("scroll_view_center", .{});
|
||||
}
|
||||
tui.need_render();
|
||||
}
|
||||
|
|
|
|||
58
src/tui/mode/mini/goto_offset.zig
Normal file
58
src/tui/mode/mini/goto_offset.zig
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
const fmt = @import("std").fmt;
|
||||
const command = @import("command");
|
||||
|
||||
const tui = @import("../../tui.zig");
|
||||
const Cursor = @import("../../editor.zig").Cursor;
|
||||
|
||||
pub const Type = @import("numeric_input.zig").Create(@This());
|
||||
pub const create = Type.create;
|
||||
|
||||
pub const ValueType = struct {
|
||||
cursor: Cursor = .{},
|
||||
offset: usize = 0,
|
||||
};
|
||||
|
||||
pub fn name(_: *Type) []const u8 {
|
||||
return "#goto byte";
|
||||
}
|
||||
|
||||
pub fn start(_: *Type) ValueType {
|
||||
const editor = tui.get_active_editor() orelse return .{};
|
||||
return .{ .cursor = editor.get_primary().cursor };
|
||||
}
|
||||
|
||||
pub fn process_digit(self: *Type, digit: u8) void {
|
||||
switch (digit) {
|
||||
0...9 => {
|
||||
if (self.input) |*input| {
|
||||
input.offset = input.offset * 10 + digit;
|
||||
} else {
|
||||
self.input = .{ .offset = digit };
|
||||
}
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete(self: *Type, input: *ValueType) void {
|
||||
const newval = if (input.offset < 10) 0 else input.offset / 10;
|
||||
if (newval == 0) self.input = null else input.offset = newval;
|
||||
}
|
||||
|
||||
pub fn format_value(_: *Type, input_: ?ValueType, buf: []u8) []const u8 {
|
||||
return if (input_) |input|
|
||||
fmt.bufPrint(buf, "{d}", .{input.offset}) catch ""
|
||||
else
|
||||
"";
|
||||
}
|
||||
|
||||
pub const preview = goto;
|
||||
pub const apply = goto;
|
||||
pub const cancel = goto;
|
||||
|
||||
fn goto(self: *Type, _: command.Context) void {
|
||||
if (self.input) |input|
|
||||
command.executeName("goto_byte_offset", command.fmt(.{input.offset})) catch {}
|
||||
else
|
||||
command.executeName("goto_line_and_column", command.fmt(.{ self.start.cursor.row, self.start.cursor.col })) catch {};
|
||||
}
|
||||
|
|
@ -1038,6 +1038,11 @@ const cmds = struct {
|
|||
}
|
||||
pub const goto_meta: Meta = .{ .description = "Goto line" };
|
||||
|
||||
pub fn goto_offset(self: *Self, ctx: Ctx) Result {
|
||||
return enter_mini_mode(self, @import("mode/mini/goto_offset.zig"), ctx);
|
||||
}
|
||||
pub const goto_offset_meta: Meta = .{ .description = "Goto byte offset" };
|
||||
|
||||
pub fn move_to_char(self: *Self, ctx: Ctx) Result {
|
||||
return enter_mini_mode(self, @import("mode/mini/move_to_char.zig"), ctx);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -190,6 +190,14 @@ test "get_byte_pos" {
|
|||
try std.testing.expectEqual(33, try buffer.root.get_byte_pos(.{ .row = 4, .col = 0 }, metrics(), eol_mode));
|
||||
try std.testing.expectEqual(66, try buffer.root.get_byte_pos(.{ .row = 8, .col = 0 }, metrics(), eol_mode));
|
||||
try std.testing.expectEqual(97, try buffer.root.get_byte_pos(.{ .row = 11, .col = 2 }, metrics(), eol_mode));
|
||||
|
||||
eol_mode = .crlf;
|
||||
try std.testing.expectEqual(0, try buffer.root.get_byte_pos(.{ .row = 0, .col = 0 }, metrics(), eol_mode));
|
||||
try std.testing.expectEqual(10, try buffer.root.get_byte_pos(.{ .row = 1, .col = 0 }, metrics(), eol_mode));
|
||||
try std.testing.expectEqual(12, try buffer.root.get_byte_pos(.{ .row = 1, .col = 2 }, metrics(), eol_mode));
|
||||
try std.testing.expectEqual(37, try buffer.root.get_byte_pos(.{ .row = 4, .col = 0 }, metrics(), eol_mode));
|
||||
try std.testing.expectEqual(74, try buffer.root.get_byte_pos(.{ .row = 8, .col = 0 }, metrics(), eol_mode));
|
||||
try std.testing.expectEqual(108, try buffer.root.get_byte_pos(.{ .row = 11, .col = 2 }, metrics(), eol_mode));
|
||||
}
|
||||
|
||||
test "delete_bytes" {
|
||||
|
|
@ -406,3 +414,44 @@ test "get_from_pos" {
|
|||
const result3 = buffer.root.get_from_pos(.{ .row = 1, .col = 5 }, &result_buf, metrics());
|
||||
try std.testing.expectEqualDeep(result3[0 .. line1.len - 4], line1[4..]);
|
||||
}
|
||||
|
||||
test "byte_offset_to_line_and_col" {
|
||||
const doc: []const u8 =
|
||||
\\All your
|
||||
\\ropes
|
||||
\\are belong to
|
||||
\\us!
|
||||
\\All your
|
||||
\\ropes
|
||||
\\are belong to
|
||||
\\us!
|
||||
\\All your
|
||||
\\ropes
|
||||
\\are belong to
|
||||
\\us!
|
||||
;
|
||||
var eol_mode: Buffer.EolMode = .lf;
|
||||
var sanitized: bool = false;
|
||||
const buffer = try Buffer.create(a);
|
||||
defer buffer.deinit();
|
||||
buffer.update(try buffer.load_from_string(doc, &eol_mode, &sanitized));
|
||||
|
||||
try std.testing.expectEqual(Buffer.Cursor{ .row = 0, .col = 0 }, buffer.root.byte_offset_to_line_and_col(0, metrics(), eol_mode));
|
||||
try std.testing.expectEqual(Buffer.Cursor{ .row = 0, .col = 8 }, buffer.root.byte_offset_to_line_and_col(8, metrics(), eol_mode));
|
||||
try std.testing.expectEqual(Buffer.Cursor{ .row = 1, .col = 0 }, buffer.root.byte_offset_to_line_and_col(9, metrics(), eol_mode));
|
||||
try std.testing.expectEqual(Buffer.Cursor{ .row = 1, .col = 2 }, buffer.root.byte_offset_to_line_and_col(11, metrics(), eol_mode));
|
||||
try std.testing.expectEqual(Buffer.Cursor{ .row = 4, .col = 0 }, buffer.root.byte_offset_to_line_and_col(33, metrics(), eol_mode));
|
||||
try std.testing.expectEqual(Buffer.Cursor{ .row = 8, .col = 0 }, buffer.root.byte_offset_to_line_and_col(66, metrics(), eol_mode));
|
||||
try std.testing.expectEqual(Buffer.Cursor{ .row = 11, .col = 2 }, buffer.root.byte_offset_to_line_and_col(97, metrics(), eol_mode));
|
||||
|
||||
eol_mode = .crlf;
|
||||
|
||||
try std.testing.expectEqual(Buffer.Cursor{ .row = 0, .col = 0 }, buffer.root.byte_offset_to_line_and_col(0, metrics(), eol_mode));
|
||||
try std.testing.expectEqual(Buffer.Cursor{ .row = 0, .col = 8 }, buffer.root.byte_offset_to_line_and_col(8, metrics(), eol_mode));
|
||||
try std.testing.expectEqual(Buffer.Cursor{ .row = 0, .col = 8 }, buffer.root.byte_offset_to_line_and_col(9, metrics(), eol_mode));
|
||||
try std.testing.expectEqual(Buffer.Cursor{ .row = 1, .col = 0 }, buffer.root.byte_offset_to_line_and_col(10, metrics(), eol_mode));
|
||||
try std.testing.expectEqual(Buffer.Cursor{ .row = 1, .col = 2 }, buffer.root.byte_offset_to_line_and_col(12, metrics(), eol_mode));
|
||||
try std.testing.expectEqual(Buffer.Cursor{ .row = 4, .col = 0 }, buffer.root.byte_offset_to_line_and_col(37, metrics(), eol_mode));
|
||||
try std.testing.expectEqual(Buffer.Cursor{ .row = 8, .col = 0 }, buffer.root.byte_offset_to_line_and_col(74, metrics(), eol_mode));
|
||||
try std.testing.expectEqual(Buffer.Cursor{ .row = 11, .col = 2 }, buffer.root.byte_offset_to_line_and_col(108, metrics(), eol_mode));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue