Merge branch 'master' into zig-0.15
This commit is contained in:
commit
96e8100373
24 changed files with 894 additions and 145 deletions
175
contrib/make_nightly_build
Executable file
175
contrib/make_nightly_build
Executable file
|
|
@ -0,0 +1,175 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--no-github) NO_GITHUB=1 ;;
|
||||
--no-codeberg) NO_CODEBERG=1 ;;
|
||||
--no-flowcontrol) NO_FLOWCONTROL=1 ;;
|
||||
--allow-dirty) ALLOW_DIRTY=1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
builddir="nightly-build"
|
||||
|
||||
DESTDIR="$(pwd)/$builddir"
|
||||
BASEDIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
APPNAME="$(basename "$BASEDIR")"
|
||||
title="$APPNAME nightly build"
|
||||
repo="neurocyte/$APPNAME-nightly"
|
||||
|
||||
release_notes="$BASEDIR/$builddir-release-notes"
|
||||
|
||||
cd "$BASEDIR"
|
||||
|
||||
if [ -e "$DESTDIR" ]; then
|
||||
echo directory \"$builddir\" already exists
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -e "$release_notes" ]; then
|
||||
echo file \""$release_notes"\" already exists
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DIFF="$(git diff --stat --patch HEAD)"
|
||||
|
||||
if [ -z "$ALLOW_DIRTY" ]; then
|
||||
if [ -n "$DIFF" ]; then
|
||||
echo there are outstanding changes:
|
||||
echo "$DIFF"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
UNPUSHED="$(git log --pretty=oneline '@{u}...')"
|
||||
|
||||
if [ -n "$UNPUSHED" ]; then
|
||||
echo there are unpushed commits:
|
||||
echo "$UNPUSHED"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# get latest version tag
|
||||
|
||||
if [ -z "$NO_FLOWCONTROL" ]; then
|
||||
last_nightly_version=$(curl -s https://git.flow-control.dev/api/v1/repos/neurocyte/flow-nightly/releases/latest | jq -r .tag_name)
|
||||
elif [ -z "$NO_GITHUB" ]; then
|
||||
last_nightly_version=$(curl -s "https://api.github.com/repos/$repo/releases/latest" | jq -r .tag_name)
|
||||
elif [ -z "$NO_CODEBERG" ]; then
|
||||
last_nightly_version=$(curl -s https://codeberg.org/api/v1/repos/neurocyte/flow-nightly/releases/latest | jq -r .tag_name)
|
||||
fi
|
||||
[ -z "$last_nightly_version" ] && {
|
||||
echo "failed to fetch $title latest version"
|
||||
exit 1
|
||||
}
|
||||
|
||||
local_version="$(git --git-dir "$BASEDIR/.git" describe)"
|
||||
if [ "$1" != "--no-github" ]; then
|
||||
if [ "$local_version" == "$last_nightly_version" ]; then
|
||||
echo "$title is already at version $last_nightly_version"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "building $title version $local_version... (previous $last_nightly_version)"
|
||||
echo
|
||||
git log "${last_nightly_version}..HEAD" --pretty="format:neurocyte/$APPNAME@%h %s"
|
||||
echo
|
||||
echo running tests...
|
||||
|
||||
./zig build test
|
||||
|
||||
echo building...
|
||||
|
||||
./zig build -Dpackage_release --prefix "$DESTDIR/build"
|
||||
|
||||
VERSION=$(/bin/cat "$DESTDIR/build/version")
|
||||
|
||||
git archive --format=tar.gz --output="$DESTDIR/flow-$VERSION-source.tar.gz" HEAD
|
||||
git archive --format=zip --output="$DESTDIR/flow-$VERSION-source.zip" HEAD
|
||||
|
||||
cd "$DESTDIR/build"
|
||||
|
||||
TARGETS=$(/bin/ls)
|
||||
|
||||
for target in $TARGETS; do
|
||||
if [ -d "$target" ]; then
|
||||
cd "$target"
|
||||
if [ "${target:0:8}" == "windows-" ]; then
|
||||
echo packing zip "$target"...
|
||||
zip -r "../../${APPNAME}-${VERSION}-${target}.zip" ./*
|
||||
cd ..
|
||||
else
|
||||
echo packing tar "$target"...
|
||||
tar -czf "../../${APPNAME}-${VERSION}-${target}.tar.gz" -- *
|
||||
cd ..
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
cd ..
|
||||
rm -r build
|
||||
|
||||
TARFILES=$(/bin/ls)
|
||||
|
||||
for tarfile in $TARFILES; do
|
||||
echo signing "$tarfile"...
|
||||
gpg --local-user 4E6CF7234FFC4E14531074F98EB1E1BB660E3FB9 --detach-sig "$tarfile"
|
||||
sha256sum -b "$tarfile" >"${tarfile}.sha256"
|
||||
done
|
||||
|
||||
echo "done making $title $VERSION @ $DESTDIR"
|
||||
echo
|
||||
|
||||
/bin/ls -lah
|
||||
|
||||
cd ..
|
||||
|
||||
{
|
||||
echo "## commits in this build"
|
||||
echo
|
||||
git log "${last_nightly_version}..HEAD" --pretty="format:neurocyte/$APPNAME@%h %s"
|
||||
echo
|
||||
echo
|
||||
|
||||
echo "## contributors"
|
||||
git shortlog -s -n "${last_nightly_version}..HEAD" | cut -b 8-
|
||||
echo
|
||||
|
||||
echo "## downloads"
|
||||
echo "[flow-control.dev](https://git.flow-control.dev/neurocyte/flow-nightly/releases/tag/$VERSION) (source only)"
|
||||
echo "[github.com](https://github.com/neurocyte/flow-nightly/releases/tag/$VERSION) (binaries & source)"
|
||||
echo "[codeberg.org](https://codeberg.org/neurocyte/flow-nightly/releases/tag/$VERSION) (binaries & source)"
|
||||
} >"$release_notes"
|
||||
|
||||
cat "$release_notes"
|
||||
|
||||
ASSETS=""
|
||||
|
||||
if [ -z "$NO_FLOWCONTROL" ]; then
|
||||
ASSETS="$ASSETS --asset $DESTDIR/flow-${VERSION}-source.tar.gz"
|
||||
ASSETS="$ASSETS --asset $DESTDIR/flow-${VERSION}-source.tar.gz.sig"
|
||||
ASSETS="$ASSETS --asset $DESTDIR/flow-${VERSION}-source.tar.gz.sha256"
|
||||
ASSETS="$ASSETS --asset $DESTDIR/flow-${VERSION}-source.zip"
|
||||
ASSETS="$ASSETS --asset $DESTDIR/flow-${VERSION}-source.zip.sig"
|
||||
ASSETS="$ASSETS --asset $DESTDIR/flow-${VERSION}-source.zip.sha256"
|
||||
echo uploading to git.flow-control.dev
|
||||
tea releases create --login flow-control --repo "$repo" --tag "$VERSION" --title "$title $VERSION" --note-file "$release_notes" \
|
||||
$ASSETS
|
||||
fi
|
||||
|
||||
if [ -z "$NO_CODEBERG" ]; then
|
||||
for a in $DESTDIR/*; do
|
||||
ASSETS="$ASSETS --asset $a"
|
||||
done
|
||||
echo uploading to codeberg.org
|
||||
tea releases create --login codeberg --repo "$repo" --tag "$VERSION" --title "$title $VERSION" --note-file "$release_notes" \
|
||||
$ASSETS
|
||||
fi
|
||||
|
||||
if [ -z "$NO_GITHUB" ]; then
|
||||
echo uploading to github.com
|
||||
gh release create "$VERSION" --repo "$repo" --title "$title $VERSION" --notes-file "$release_notes" $DESTDIR/*
|
||||
fi
|
||||
|
|
@ -1005,10 +1005,17 @@ fn send_completion_items(to: tp.pid_ref, file_path: []const u8, row: usize, col:
|
|||
var item: []const u8 = "";
|
||||
while (len > 0) : (len -= 1) {
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract_cbor(&item)))) return error.InvalidMessageField;
|
||||
send_completion_item(to, file_path, row, col, item, if (len > 1) true else is_incomplete) catch return error.ClientFailed;
|
||||
try send_completion_item(to, file_path, row, col, item, if (len > 1) true else is_incomplete);
|
||||
}
|
||||
}
|
||||
|
||||
fn invalid_field(field: []const u8) error{InvalidMessage} {
|
||||
const logger = log.logger("lsp");
|
||||
defer logger.deinit();
|
||||
logger.print("invalid completion field '{s}'", .{field});
|
||||
return error.InvalidMessage;
|
||||
}
|
||||
|
||||
fn send_completion_item(to: tp.pid_ref, file_path: []const u8, row: usize, col: usize, item: []const u8, is_incomplete: bool) (ClientError || InvalidMessageError || cbor.Error)!void {
|
||||
var label: []const u8 = "";
|
||||
var label_detail: []const u8 = "";
|
||||
|
|
@ -1029,53 +1036,53 @@ fn send_completion_item(to: tp.pid_ref, file_path: []const u8, row: usize, col:
|
|||
var field_name: []const u8 = undefined;
|
||||
if (!(try cbor.matchString(&iter, &field_name))) return error.InvalidMessage;
|
||||
if (std.mem.eql(u8, field_name, "label")) {
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract(&label)))) return error.InvalidMessageField;
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract(&label)))) return invalid_field("label");
|
||||
} else if (std.mem.eql(u8, field_name, "labelDetails")) {
|
||||
var len_ = cbor.decodeMapHeader(&iter) catch return;
|
||||
while (len_ > 0) : (len_ -= 1) {
|
||||
if (!(try cbor.matchString(&iter, &field_name))) return error.InvalidMessage;
|
||||
if (!(try cbor.matchString(&iter, &field_name))) return invalid_field("labelDetails");
|
||||
if (std.mem.eql(u8, field_name, "detail")) {
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract(&label_detail)))) return error.InvalidMessageField;
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract(&label_detail)))) return invalid_field("labelDetails.detail");
|
||||
} else if (std.mem.eql(u8, field_name, "description")) {
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract(&label_description)))) return error.InvalidMessageField;
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract(&label_description)))) return invalid_field("labelDetails.description");
|
||||
} else {
|
||||
try cbor.skipValue(&iter);
|
||||
}
|
||||
}
|
||||
} else if (std.mem.eql(u8, field_name, "kind")) {
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract(&kind)))) return error.InvalidMessageField;
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract(&kind)))) return invalid_field("kind");
|
||||
} else if (std.mem.eql(u8, field_name, "detail")) {
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract(&detail)))) return error.InvalidMessageField;
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract(&detail)))) return invalid_field("detail");
|
||||
} else if (std.mem.eql(u8, field_name, "documentation")) {
|
||||
var len_ = cbor.decodeMapHeader(&iter) catch return;
|
||||
while (len_ > 0) : (len_ -= 1) {
|
||||
if (!(try cbor.matchString(&iter, &field_name))) return error.InvalidMessage;
|
||||
if (!(try cbor.matchString(&iter, &field_name))) return invalid_field("documentation");
|
||||
if (std.mem.eql(u8, field_name, "kind")) {
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract(&documentation_kind)))) return error.InvalidMessageField;
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract(&documentation_kind)))) return invalid_field("documentation.kind");
|
||||
} else if (std.mem.eql(u8, field_name, "value")) {
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract(&documentation)))) return error.InvalidMessageField;
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract(&documentation)))) return invalid_field("documentation.value");
|
||||
} else {
|
||||
try cbor.skipValue(&iter);
|
||||
}
|
||||
}
|
||||
} else if (std.mem.eql(u8, field_name, "sortText")) {
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract(&sortText)))) return error.InvalidMessageField;
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract(&sortText)))) return invalid_field("sortText");
|
||||
} else if (std.mem.eql(u8, field_name, "insertTextFormat")) {
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract(&insertTextFormat)))) return error.InvalidMessageField;
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract(&insertTextFormat)))) return invalid_field("insertTextFormat");
|
||||
} else if (std.mem.eql(u8, field_name, "textEdit")) {
|
||||
// var textEdit: []const u8 = ""; // { "newText": "wait_expired(${1:timeout_ns: isize})", "insert": Range, "replace": Range },
|
||||
var len_ = cbor.decodeMapHeader(&iter) catch return;
|
||||
while (len_ > 0) : (len_ -= 1) {
|
||||
if (!(try cbor.matchString(&iter, &field_name))) return error.InvalidMessage;
|
||||
if (!(try cbor.matchString(&iter, &field_name))) return invalid_field("textEdit");
|
||||
if (std.mem.eql(u8, field_name, "newText")) {
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract(&textEdit_newText)))) return error.InvalidMessageField;
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract(&textEdit_newText)))) return invalid_field("textEdit.newText");
|
||||
} else if (std.mem.eql(u8, field_name, "insert")) {
|
||||
var range_: []const u8 = undefined;
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract_cbor(&range_)))) return error.InvalidMessageField;
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract_cbor(&range_)))) return invalid_field("textEdit.insert");
|
||||
textEdit_insert = try read_range(range_);
|
||||
} else if (std.mem.eql(u8, field_name, "replace")) {
|
||||
var range_: []const u8 = undefined;
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract_cbor(&range_)))) return error.InvalidMessageField;
|
||||
if (!(try cbor.matchValue(&iter, cbor.extract_cbor(&range_)))) return invalid_field("textEdit.replace");
|
||||
textEdit_replace = try read_range(range_);
|
||||
} else {
|
||||
try cbor.skipValue(&iter);
|
||||
|
|
@ -1085,8 +1092,8 @@ fn send_completion_item(to: tp.pid_ref, file_path: []const u8, row: usize, col:
|
|||
try cbor.skipValue(&iter);
|
||||
}
|
||||
}
|
||||
const insert = textEdit_insert orelse return error.InvalidMessageField;
|
||||
const replace = textEdit_replace orelse return error.InvalidMessageField;
|
||||
const insert = textEdit_insert orelse Range{ .start = .{ .line = 0, .character = 0 }, .end = .{ .line = 0, .character = 0 } };
|
||||
const replace = textEdit_replace orelse Range{ .start = .{ .line = 0, .character = 0 }, .end = .{ .line = 0, .character = 0 } };
|
||||
return to.send(.{
|
||||
"cmd", "add_completion", .{
|
||||
file_path,
|
||||
|
|
|
|||
|
|
@ -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 } });
|
||||
|
|
|
|||
|
|
@ -77,12 +77,20 @@
|
|||
["ctrl+enter", "smart_insert_line_after"],
|
||||
["ctrl+end", "move_buffer_end"],
|
||||
["ctrl+home", "move_buffer_begin"],
|
||||
["ctrl+kp_end", "move_buffer_end"],
|
||||
["ctrl+kp_home", "move_buffer_begin"],
|
||||
["ctrl+up", "move_scroll_up"],
|
||||
["ctrl+down", "move_scroll_down"],
|
||||
["ctrl+kp_up", "move_scroll_up"],
|
||||
["ctrl+kp_down", "move_scroll_down"],
|
||||
["ctrl+page_up", "move_scroll_page_up"],
|
||||
["ctrl+page_down", "move_scroll_page_down"],
|
||||
["ctrl+kp_page_up", "move_scroll_page_up"],
|
||||
["ctrl+kp_page_down", "move_scroll_page_down"],
|
||||
["ctrl+left", "move_word_left"],
|
||||
["ctrl+right", "move_word_right"],
|
||||
["ctrl+kp_left", "move_word_left"],
|
||||
["ctrl+kp_right", "move_word_right"],
|
||||
["ctrl+backspace", "delete_word_left"],
|
||||
["ctrl+delete", "delete_word_right"],
|
||||
["ctrl+f5", "toggle_inspector_view"],
|
||||
|
|
@ -98,10 +106,16 @@
|
|||
["ctrl+shift+enter", "smart_insert_line_before"],
|
||||
["ctrl+shift+end", "select_buffer_end"],
|
||||
["ctrl+shift+home", "select_buffer_begin"],
|
||||
["ctrl+shift+kp_end", "select_buffer_end"],
|
||||
["ctrl+shift+kp_home", "select_buffer_begin"],
|
||||
["ctrl+shift+up", "select_scroll_up"],
|
||||
["ctrl+shift+down", "select_scroll_down"],
|
||||
["ctrl+shift+kp_up", "select_scroll_up"],
|
||||
["ctrl+shift+kp_down", "select_scroll_down"],
|
||||
["ctrl+shift+left", "select_word_left"],
|
||||
["ctrl+shift+right", "select_word_right"],
|
||||
["ctrl+shift+kp_left", "select_word_left"],
|
||||
["ctrl+shift+kp_right", "select_word_right"],
|
||||
["ctrl+shift+space", "selections_reverse"],
|
||||
["alt+o", "open_previous_file"],
|
||||
["alt+j", "join_next_line"],
|
||||
|
|
@ -117,8 +131,12 @@
|
|||
["alt+R", ["shell_execute_insert", "openssl", "rand", "-hex", "4"]],
|
||||
["alt+left", "jump_back"],
|
||||
["alt+right", "jump_forward"],
|
||||
["alt+kp_left", "jump_back"],
|
||||
["alt+kp_right", "jump_forward"],
|
||||
["alt+up", "pull_up"],
|
||||
["alt+down", "pull_down"],
|
||||
["alt+kp_up", "pull_up"],
|
||||
["alt+kp_down", "pull_down"],
|
||||
["alt+enter", "insert_line"],
|
||||
["alt+f10", "gutter_mode_next"],
|
||||
["alt+shift+f10", "gutter_style_next"],
|
||||
|
|
@ -128,29 +146,49 @@
|
|||
["alt+shift+s", "filter", "sort", "-u"],
|
||||
["alt+shift+v", "paste"],
|
||||
["alt+shift+i", "add_cursors_to_line_ends"],
|
||||
["alt+shift+left", "shrink_selection"],
|
||||
["alt+shift+right", "expand_selection"],
|
||||
["alt+shift+left", "expand_selection"],
|
||||
["alt+shift+right", "shrink_selection"],
|
||||
["alt+shift+kp_left", "expand_selection"],
|
||||
["alt+shift+kp_right", "shrink_selection"],
|
||||
["alt+home", "select_prev_sibling"],
|
||||
["alt+end", "select_next_sibling"],
|
||||
["alt+kp_home", "select_prev_sibling"],
|
||||
["alt+kp_end", "select_next_sibling"],
|
||||
["alt+{", "expand_selection"],
|
||||
["alt+}", "shrink_selection", true],
|
||||
["alt+[", "select_prev_sibling", true],
|
||||
["alt+]", "select_next_sibling", true],
|
||||
["alt+shift+e", "move_parent_node_end"],
|
||||
["alt+shift+b", "move_parent_node_start"],
|
||||
["alt+a", "select_all_siblings"],
|
||||
["alt+shift+home", "move_scroll_left"],
|
||||
["alt+shift+end", "move_scroll_right"],
|
||||
["alt+shift+kp_home", "move_scroll_left"],
|
||||
["alt+shift+kp_end", "move_scroll_right"],
|
||||
["alt+shift+up", "add_cursor_up"],
|
||||
["alt+shift+down", "add_cursor_down"],
|
||||
["alt+shift+kp_up", "add_cursor_up"],
|
||||
["alt+shift+kp_down", "add_cursor_down"],
|
||||
["alt+shift+f12", "goto_type_definition"],
|
||||
["shift+f3", "goto_prev_match"],
|
||||
["shift+f10", "toggle_syntax_highlighting"],
|
||||
["shift+f12", "references"],
|
||||
["shift+left", "select_left"],
|
||||
["shift+right", "select_right"],
|
||||
["shift+kp_left", "select_left"],
|
||||
["shift+kp_right", "select_right"],
|
||||
["shift+up", "select_up"],
|
||||
["shift+down", "select_down"],
|
||||
["shift+kp_up", "select_up"],
|
||||
["shift+kp_down", "select_down"],
|
||||
["shift+home", "smart_select_begin"],
|
||||
["shift+end", "select_end"],
|
||||
["shift+kp_home", "smart_select_begin"],
|
||||
["shift+kp_end", "select_end"],
|
||||
["shift+page_up", "select_page_up"],
|
||||
["shift+page_down", "select_page_down"],
|
||||
["shift+kp_page_up", "select_page_up"],
|
||||
["shift+kp_page_down", "select_page_down"],
|
||||
["shift+enter", "smart_insert_line_before"],
|
||||
["shift+backspace", "delete_backward"],
|
||||
["shift+tab", "unindent"],
|
||||
|
|
@ -173,12 +211,20 @@
|
|||
["backspace", "smart_delete_backward"],
|
||||
["left", "move_left"],
|
||||
["right", "move_right"],
|
||||
["kp_left", "move_left"],
|
||||
["kp_right", "move_right"],
|
||||
["up", "move_up"],
|
||||
["down", "move_down"],
|
||||
["kp_up", "move_up"],
|
||||
["kp_down", "move_down"],
|
||||
["home", "smart_move_begin"],
|
||||
["end", "move_end"],
|
||||
["kp_home", "smart_move_begin"],
|
||||
["kp_end", "move_end"],
|
||||
["page_up", "move_page_up"],
|
||||
["page_down", "move_page_down"],
|
||||
["kp_page_up", "move_page_up"],
|
||||
["kp_page_down", "move_page_down"],
|
||||
["tab", "indent"],
|
||||
["ctrl+space", "enter_mode", "select"],
|
||||
|
||||
|
|
@ -231,16 +277,30 @@
|
|||
["right", "select_right"],
|
||||
["ctrl+left", "select_word_left"],
|
||||
["ctrl+right", "select_word_right"],
|
||||
["kp_left", "select_left"],
|
||||
["kp_right", "select_right"],
|
||||
["ctrl+kp_left", "select_word_left"],
|
||||
["ctrl+kp_right", "select_word_right"],
|
||||
["up", "select_up"],
|
||||
["down", "select_down"],
|
||||
["kp_up", "select_up"],
|
||||
["kp_down", "select_down"],
|
||||
["home", "select_begin"],
|
||||
["end", "select_end"],
|
||||
["kp_home", "select_begin"],
|
||||
["kp_end", "select_end"],
|
||||
["ctrl+home", "select_buffer_begin"],
|
||||
["ctrl+end", "select_buffer_end"],
|
||||
["ctrl+kp_home", "select_buffer_begin"],
|
||||
["ctrl+kp_end", "select_buffer_end"],
|
||||
["page_up", "select_page_up"],
|
||||
["page_down", "select_page_down"],
|
||||
["ctrl+page_up", "select_scroll_page_up"],
|
||||
["ctrl+page_down", "select_scroll_page_down"],
|
||||
["kp_page_up", "select_page_up"],
|
||||
["kp_page_down", "select_page_down"],
|
||||
["ctrl+kp_page_up", "select_scroll_page_up"],
|
||||
["ctrl+kp_page_down", "select_scroll_page_down"],
|
||||
["ctrl+b", "move_to_char", "select_to_char_left"],
|
||||
["ctrl+t", "move_to_char", "select_to_char_right"],
|
||||
["ctrl+space", "enter_mode", "normal"],
|
||||
|
|
@ -282,6 +342,8 @@
|
|||
["q", "quit"],
|
||||
["up", "home_menu_up"],
|
||||
["down", "home_menu_down"],
|
||||
["kp_up", "home_menu_up"],
|
||||
["kp_down", "home_menu_down"],
|
||||
["enter", "home_menu_activate"]
|
||||
]
|
||||
},
|
||||
|
|
@ -304,8 +366,12 @@
|
|||
["ctrl+escape", "palette_menu_cancel"],
|
||||
["ctrl+up", "palette_menu_up"],
|
||||
["ctrl+down", "palette_menu_down"],
|
||||
["ctrl+kp_up", "palette_menu_up"],
|
||||
["ctrl+kp_down", "palette_menu_down"],
|
||||
["ctrl+page_up", "palette_menu_pageup"],
|
||||
["ctrl+page_down", "palette_menu_pagedown"],
|
||||
["ctrl+kp_page_up", "palette_menu_pageup"],
|
||||
["ctrl+kp_page_down", "palette_menu_pagedown"],
|
||||
["ctrl+enter", "palette_menu_activate"],
|
||||
["ctrl+backspace", "overlay_delete_word_left"],
|
||||
["ctrl+shift+e", "palette_menu_up"],
|
||||
|
|
@ -326,10 +392,16 @@
|
|||
["escape", "palette_menu_cancel"],
|
||||
["up", "palette_menu_up"],
|
||||
["down", "palette_menu_down"],
|
||||
["kp_up", "palette_menu_up"],
|
||||
["kp_down", "palette_menu_down"],
|
||||
["page_up", "palette_menu_pageup"],
|
||||
["page_down", "palette_menu_pagedown"],
|
||||
["kp_page_up", "palette_menu_pageup"],
|
||||
["kp_page_down", "palette_menu_pagedown"],
|
||||
["home", "palette_menu_top"],
|
||||
["end", "palette_menu_bottom"],
|
||||
["kp_home", "palette_menu_top"],
|
||||
["kp_end", "palette_menu_bottom"],
|
||||
["enter", "palette_menu_activate"],
|
||||
["delete", "palette_menu_delete_item"],
|
||||
["backspace", "overlay_delete_backwards"]
|
||||
|
|
@ -341,6 +413,7 @@
|
|||
},
|
||||
"mini/numeric": {
|
||||
"press": [
|
||||
["b", "goto_offset"],
|
||||
["ctrl+q", "quit"],
|
||||
["ctrl+v", "system_paste"],
|
||||
["ctrl+u", "mini_mode_reset"],
|
||||
|
|
@ -399,8 +472,12 @@
|
|||
["shift+tab", "mini_mode_reverse_complete_file"],
|
||||
["up", "mini_mode_reverse_complete_file"],
|
||||
["down", "mini_mode_try_complete_file"],
|
||||
["right", "mini_mode_try_complete_file_forward"],
|
||||
["kp_up", "mini_mode_reverse_complete_file"],
|
||||
["kp_down", "mini_mode_try_complete_file"],
|
||||
["left", "mini_mode_delete_to_previous_path_segment"],
|
||||
["right", "mini_mode_try_complete_file_forward"],
|
||||
["kp_left", "mini_mode_delete_to_previous_path_segment"],
|
||||
["kp_right", "mini_mode_try_complete_file_forward"],
|
||||
["tab", "mini_mode_try_complete_file"],
|
||||
["escape", "mini_mode_cancel"],
|
||||
["enter", "mini_mode_select"],
|
||||
|
|
@ -430,6 +507,8 @@
|
|||
["shift+f3", "goto_prev_match"],
|
||||
["up", "select_prev_file"],
|
||||
["down", "select_next_file"],
|
||||
["kp_up", "select_prev_file"],
|
||||
["kp_down", "select_next_file"],
|
||||
["f3", "goto_next_match"],
|
||||
["f15", "goto_prev_match"],
|
||||
["f9", "theme_prev"],
|
||||
|
|
@ -462,6 +541,8 @@
|
|||
["shift+f3", "goto_prev_match"],
|
||||
["up", "mini_mode_history_prev"],
|
||||
["down", "mini_mode_history_next"],
|
||||
["kp_up", "mini_mode_history_prev"],
|
||||
["kp_down", "mini_mode_history_next"],
|
||||
["f3", "goto_next_match"],
|
||||
["f15", "goto_prev_match"],
|
||||
["f9", "theme_prev"],
|
||||
|
|
|
|||
|
|
@ -111,6 +111,8 @@
|
|||
|
||||
["home", "move_begin"],
|
||||
["end", "move_end"],
|
||||
["kp_home", "move_begin"],
|
||||
["kp_end", "move_end"],
|
||||
|
||||
["w","move_next_word_start"],
|
||||
["b","move_prev_word_start"],
|
||||
|
|
@ -201,6 +203,8 @@
|
|||
|
||||
["page_up", "move_scroll_page_up"],
|
||||
["page_down", "move_scroll_page_down"],
|
||||
["kp_page_up", "move_scroll_page_up"],
|
||||
["kp_page_down", "move_scroll_page_down"],
|
||||
|
||||
["space F", "find_file"],
|
||||
["space S", "workspace_symbol_picker"],
|
||||
|
|
@ -293,6 +297,10 @@
|
|||
["alt+down", "shrink_selection"],
|
||||
["alt+left", "select_prev_sibling"],
|
||||
["alt+right", "select_next_sibling"],
|
||||
["alt+kp_up", "expand_selection"],
|
||||
["alt+kp_down", "shrink_selection"],
|
||||
["alt+kp_left", "select_prev_sibling"],
|
||||
["alt+kp_right", "select_next_sibling"],
|
||||
|
||||
["alt+e", "extend_parent_node_end"],
|
||||
["alt+b", "extend_parent_node_start"],
|
||||
|
|
@ -378,6 +386,10 @@
|
|||
["down", "select_down"],
|
||||
["up", "select_up"],
|
||||
["right", "select_right"],
|
||||
["kp_left", "select_left"],
|
||||
["kp_down", "select_down"],
|
||||
["kp_up", "select_up"],
|
||||
["kp_right", "select_right"],
|
||||
|
||||
["t", "extend_till_char"],
|
||||
["f", "move_to_char", "select_to_char_right_helix"],
|
||||
|
|
@ -386,6 +398,8 @@
|
|||
|
||||
["home", "extend_to_line_start"],
|
||||
["end", "extend_to_line_end"],
|
||||
["kp_home", "extend_to_line_start"],
|
||||
["kp_end", "extend_to_line_end"],
|
||||
|
||||
["w", "extend_next_word_start"],
|
||||
["b", "extend_pre_word_start"],
|
||||
|
|
|
|||
42
src/main.zig
42
src/main.zig
|
|
@ -255,19 +255,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 (args.positional.trailing.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;
|
||||
}
|
||||
|
||||
|
|
@ -284,6 +299,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;
|
||||
|
|
|
|||
|
|
@ -231,6 +231,25 @@ pub const utils = struct {
|
|||
vaxis.Key.f33 => "f33",
|
||||
vaxis.Key.f34 => "f34",
|
||||
vaxis.Key.f35 => "f35",
|
||||
vaxis.Key.kp_decimal => "kp_decimal",
|
||||
vaxis.Key.kp_divide => "kp_divide",
|
||||
vaxis.Key.kp_multiply => "kp_multiply",
|
||||
vaxis.Key.kp_subtract => "kp_subtract",
|
||||
vaxis.Key.kp_add => "kp_add",
|
||||
vaxis.Key.kp_enter => "kp_enter",
|
||||
vaxis.Key.kp_equal => "kp_equal",
|
||||
vaxis.Key.kp_separator => "kp_separator",
|
||||
vaxis.Key.kp_left => "kp_left",
|
||||
vaxis.Key.kp_right => "kp_right",
|
||||
vaxis.Key.kp_up => "kp_up",
|
||||
vaxis.Key.kp_down => "kp_down",
|
||||
vaxis.Key.kp_page_up => "kp_page_up",
|
||||
vaxis.Key.kp_page_down => "kp_page_down",
|
||||
vaxis.Key.kp_home => "kp_home",
|
||||
vaxis.Key.kp_end => "kp_end",
|
||||
vaxis.Key.kp_insert => "kp_insert",
|
||||
vaxis.Key.kp_delete => "kp_delete",
|
||||
vaxis.Key.kp_begin => "kp_begin",
|
||||
vaxis.Key.media_play => "media_play",
|
||||
vaxis.Key.media_pause => "media_pause",
|
||||
vaxis.Key.media_play_pause => "media_play_pause",
|
||||
|
|
|
|||
|
|
@ -102,37 +102,41 @@ pub const CurSel = struct {
|
|||
}
|
||||
|
||||
pub fn enable_selection(self: *Self, root: Buffer.Root, metrics: Buffer.Metrics) !*Selection {
|
||||
return switch (tui.get_selection_style()) {
|
||||
.normal => self.enable_selection_normal(),
|
||||
.inclusive => try self.enable_selection_inclusive(root, metrics),
|
||||
};
|
||||
self.selection = try self.to_selection(root, metrics);
|
||||
return if (self.selection) |*sel| sel else unreachable;
|
||||
}
|
||||
|
||||
pub fn enable_selection_normal(self: *Self) *Selection {
|
||||
return if (self.selection) |*sel|
|
||||
sel
|
||||
else cod: {
|
||||
self.selection = Selection.from_cursor(&self.cursor);
|
||||
break :cod &self.selection.?;
|
||||
self.selection = self.to_selection_normal();
|
||||
return if (self.selection) |*sel| sel else unreachable;
|
||||
}
|
||||
|
||||
pub fn to_selection(self: *const Self, root: Buffer.Root, metrics: Buffer.Metrics) !Selection {
|
||||
return switch (tui.get_selection_style()) {
|
||||
.normal => self.to_selection_normal(),
|
||||
.inclusive => try self.to_selection_inclusive(root, metrics),
|
||||
};
|
||||
}
|
||||
|
||||
fn enable_selection_inclusive(self: *Self, root: Buffer.Root, metrics: Buffer.Metrics) !*Selection {
|
||||
return if (self.selection) |*sel|
|
||||
fn to_selection_normal(self: *const Self) Selection {
|
||||
return if (self.selection) |sel| sel else Selection.from_cursor(&self.cursor);
|
||||
}
|
||||
|
||||
fn to_selection_inclusive(self: *const Self, root: Buffer.Root, metrics: Buffer.Metrics) !Selection {
|
||||
return if (self.selection) |sel|
|
||||
sel
|
||||
else cod: {
|
||||
self.selection = Selection.from_cursor(&self.cursor);
|
||||
try self.selection.?.end.move_right(root, metrics);
|
||||
try self.cursor.move_right(root, metrics);
|
||||
break :cod &self.selection.?;
|
||||
var sel = Selection.from_cursor(&self.cursor);
|
||||
try sel.end.move_right(root, metrics);
|
||||
break :cod sel;
|
||||
};
|
||||
}
|
||||
|
||||
fn to_inclusive_cursor(self: *const Self, root: Buffer.Root, metrics: Buffer.Metrics) !Cursor {
|
||||
var res = self.cursor;
|
||||
fn to_cursor_inclusive(self: *const Self, root: Buffer.Root, metrics: Buffer.Metrics) !Cursor {
|
||||
var cursor = self.cursor;
|
||||
if (self.selection) |sel| if (!sel.is_reversed())
|
||||
try res.move_left(root, metrics);
|
||||
return res;
|
||||
try cursor.move_left(root, metrics);
|
||||
return cursor;
|
||||
}
|
||||
|
||||
pub fn disable_selection(self: *Self, root: Buffer.Root, metrics: Buffer.Metrics) void {
|
||||
|
|
@ -170,9 +174,8 @@ pub const CurSel = struct {
|
|||
return sel;
|
||||
}
|
||||
|
||||
fn select_node(self: *Self, node: syntax.Node, root: Buffer.Root, metrics: Buffer.Metrics) error{NotFound}!void {
|
||||
const range = node.getRange();
|
||||
self.selection = .{
|
||||
fn selection_from_range(range: syntax.Range, root: Buffer.Root, metrics: Buffer.Metrics) error{NotFound}!Selection {
|
||||
return .{
|
||||
.begin = .{
|
||||
.row = range.start_point.row,
|
||||
.col = try root.pos_to_width(range.start_point.row, range.start_point.column, metrics),
|
||||
|
|
@ -182,7 +185,23 @@ pub const CurSel = struct {
|
|||
.col = try root.pos_to_width(range.end_point.row, range.end_point.column, metrics),
|
||||
},
|
||||
};
|
||||
self.cursor = self.selection.?.end;
|
||||
}
|
||||
|
||||
fn selection_from_node(node: syntax.Node, root: Buffer.Root, metrics: Buffer.Metrics) error{NotFound}!Selection {
|
||||
return selection_from_range(node.getRange(), root, metrics);
|
||||
}
|
||||
|
||||
fn select_node(self: *Self, node: syntax.Node, root: Buffer.Root, metrics: Buffer.Metrics) error{NotFound}!void {
|
||||
const sel = try selection_from_node(node, root, metrics);
|
||||
self.selection = sel;
|
||||
self.cursor = sel.end;
|
||||
}
|
||||
|
||||
fn select_parent_node(self: *Self, node: syntax.Node, root: Buffer.Root, metrics: Buffer.Metrics) error{NotFound}!syntax.Node {
|
||||
const parent = node.getParent();
|
||||
if (parent.isNull()) return error.NotFound;
|
||||
try self.select_node(parent, root, metrics);
|
||||
return parent;
|
||||
}
|
||||
|
||||
fn write(self: *const Self, writer: Buffer.MetaWriter) !void {
|
||||
|
|
@ -1192,7 +1211,7 @@ pub const Editor = struct {
|
|||
fn get_rendered_cursor(self: *Self, style: anytype, cursel: anytype) !Cursor {
|
||||
return switch (style) {
|
||||
.normal => cursel.cursor,
|
||||
.inclusive => try cursel.to_inclusive_cursor(try self.buf_root(), self.metrics),
|
||||
.inclusive => try cursel.to_cursor_inclusive(try self.buf_root(), self.metrics),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -4279,7 +4298,7 @@ pub const Editor = struct {
|
|||
}
|
||||
pub const selections_reverse_meta: Meta = .{ .description = "Reverse selection" };
|
||||
|
||||
fn node_at_selection(self: *Self, sel: Selection, root: Buffer.Root, metrics: Buffer.Metrics) error{Stop}!syntax.Node {
|
||||
fn node_at_selection(self: *const Self, sel: Selection, root: Buffer.Root, metrics: Buffer.Metrics) error{Stop}!syntax.Node {
|
||||
const syn = self.syntax orelse return error.Stop;
|
||||
const node = try syn.node_at_point_range(.{
|
||||
.start_point = .{
|
||||
|
|
@ -4297,19 +4316,35 @@ pub const Editor = struct {
|
|||
return node;
|
||||
}
|
||||
|
||||
fn select_node_at_cursor(self: *Self, root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void {
|
||||
cursel.disable_selection(root, self.metrics);
|
||||
const sel = (try cursel.enable_selection(root, self.metrics)).*;
|
||||
return cursel.select_node(try self.node_at_selection(sel, root, metrics), root, metrics);
|
||||
fn top_node_at_selection(self: *const Self, sel: Selection, root: Buffer.Root, metrics: Buffer.Metrics) error{Stop}!syntax.Node {
|
||||
var node = try self.node_at_selection(sel, root, metrics);
|
||||
if (node.isNull()) return node;
|
||||
var parent = node.getParent();
|
||||
if (parent.isNull()) return node;
|
||||
const node_sel = CurSel.selection_from_node(node, root, metrics) catch return node;
|
||||
var parent_sel = CurSel.selection_from_node(parent, root, metrics) catch return node;
|
||||
while (parent_sel.eql(node_sel)) {
|
||||
node = parent;
|
||||
parent = parent.getParent();
|
||||
parent_sel = CurSel.selection_from_node(parent, root, metrics) catch return node;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
fn top_node_at_cursel(self: *const Self, cursel: *const CurSel, root: Buffer.Root, metrics: Buffer.Metrics) error{Stop}!syntax.Node {
|
||||
const sel = try cursel.to_selection(root, metrics);
|
||||
return try self.top_node_at_selection(sel, root, metrics);
|
||||
}
|
||||
|
||||
fn expand_selection_to_parent_node(self: *Self, root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void {
|
||||
const sel = (try cursel.enable_selection(root, metrics)).*;
|
||||
const node = try self.node_at_selection(sel, root, metrics);
|
||||
var node = try self.top_node_at_selection(sel, root, metrics);
|
||||
if (node.isNull()) return error.Stop;
|
||||
const parent = node.getParent();
|
||||
if (parent.isNull()) return error.Stop;
|
||||
return cursel.select_node(parent, root, metrics);
|
||||
var node_sel = try CurSel.selection_from_node(node, root, metrics);
|
||||
if (!node_sel.eql(sel)) return cursel.select_node(node, root, metrics);
|
||||
node = try cursel.select_parent_node(node, root, metrics);
|
||||
while (cursel.selection.?.eql(sel))
|
||||
node = try cursel.select_parent_node(node, root, metrics);
|
||||
}
|
||||
|
||||
pub fn expand_selection(self: *Self, _: Context) Result {
|
||||
|
|
@ -4320,7 +4355,7 @@ pub const Editor = struct {
|
|||
try if (cursel.selection) |_|
|
||||
self.expand_selection_to_parent_node(root, cursel, self.metrics)
|
||||
else
|
||||
self.select_node_at_cursor(root, cursel, self.metrics);
|
||||
cursel.select_node(try self.top_node_at_cursel(cursel, root, self.metrics), root, self.metrics);
|
||||
self.clamp();
|
||||
try self.send_editor_jump_destination();
|
||||
}
|
||||
|
|
@ -4362,8 +4397,7 @@ pub const Editor = struct {
|
|||
pub const shrink_selection_meta: Meta = .{ .description = "Shrink selection to first AST child node" };
|
||||
|
||||
fn select_next_sibling_node(self: *Self, root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void {
|
||||
const sel = (try cursel.enable_selection(root, metrics)).*;
|
||||
const node = try self.node_at_selection(sel, root, metrics);
|
||||
const node = try self.top_node_at_cursel(cursel, root, metrics);
|
||||
if (node.isNull()) return error.Stop;
|
||||
const sibling = syntax.Node.externs.ts_node_next_sibling(node);
|
||||
if (sibling.isNull()) return error.Stop;
|
||||
|
|
@ -4371,8 +4405,7 @@ pub const Editor = struct {
|
|||
}
|
||||
|
||||
fn select_next_named_sibling_node(self: *Self, root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void {
|
||||
const sel = (try cursel.enable_selection(root, metrics)).*;
|
||||
const node = try self.node_at_selection(sel, root, metrics);
|
||||
const node = try self.top_node_at_cursel(cursel, root, metrics);
|
||||
if (node.isNull()) return error.Stop;
|
||||
const sibling = syntax.Node.externs.ts_node_next_named_sibling(node);
|
||||
if (sibling.isNull()) return error.Stop;
|
||||
|
|
@ -4386,19 +4419,17 @@ pub const Editor = struct {
|
|||
const root = try self.buf_root();
|
||||
const cursel = self.get_primary();
|
||||
cursel.check_selection(root, self.metrics);
|
||||
if (cursel.selection) |_|
|
||||
try if (unnamed)
|
||||
self.select_next_sibling_node(root, cursel, self.metrics)
|
||||
else
|
||||
self.select_next_named_sibling_node(root, cursel, self.metrics);
|
||||
try if (unnamed)
|
||||
self.select_next_sibling_node(root, cursel, self.metrics)
|
||||
else
|
||||
self.select_next_named_sibling_node(root, cursel, self.metrics);
|
||||
self.clamp();
|
||||
try self.send_editor_jump_destination();
|
||||
}
|
||||
pub const select_next_sibling_meta: Meta = .{ .description = "Move selection to next AST sibling node" };
|
||||
|
||||
fn select_prev_sibling_node(self: *Self, root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void {
|
||||
const sel = (try cursel.enable_selection(root, metrics)).*;
|
||||
const node = try self.node_at_selection(sel, root, metrics);
|
||||
const node = try self.top_node_at_cursel(cursel, root, metrics);
|
||||
if (node.isNull()) return error.Stop;
|
||||
const sibling = syntax.Node.externs.ts_node_prev_sibling(node);
|
||||
if (sibling.isNull()) return error.Stop;
|
||||
|
|
@ -4406,8 +4437,7 @@ pub const Editor = struct {
|
|||
}
|
||||
|
||||
fn select_prev_named_sibling_node(self: *Self, root: Buffer.Root, cursel: *CurSel, metrics: Buffer.Metrics) !void {
|
||||
const sel = (try cursel.enable_selection(root, metrics)).*;
|
||||
const node = try self.node_at_selection(sel, root, metrics);
|
||||
const node = try self.top_node_at_cursel(cursel, root, metrics);
|
||||
if (node.isNull()) return error.Stop;
|
||||
const sibling = syntax.Node.externs.ts_node_prev_named_sibling(node);
|
||||
if (sibling.isNull()) return error.Stop;
|
||||
|
|
@ -4421,11 +4451,10 @@ pub const Editor = struct {
|
|||
const root = try self.buf_root();
|
||||
const cursel = self.get_primary();
|
||||
cursel.check_selection(root, self.metrics);
|
||||
if (cursel.selection) |_|
|
||||
try if (unnamed)
|
||||
self.select_prev_sibling_node(root, cursel, self.metrics)
|
||||
else
|
||||
self.select_prev_named_sibling_node(root, cursel, self.metrics);
|
||||
try if (unnamed)
|
||||
self.select_prev_sibling_node(root, cursel, self.metrics)
|
||||
else
|
||||
self.select_prev_named_sibling_node(root, cursel, self.metrics);
|
||||
self.clamp();
|
||||
try self.send_editor_jump_destination();
|
||||
}
|
||||
|
|
@ -5477,6 +5506,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();
|
||||
|
|
|
|||
|
|
@ -224,6 +224,17 @@ pub const font_test_text: []const u8 =
|
|||
\\🙂↔
|
||||
\\
|
||||
\\
|
||||
\\你好世界 "Hello World"
|
||||
\\一二三四五六七八九十 "123456789"
|
||||
\\龍鳳麟龜 (dragon, phoenix, qilin, turtle)
|
||||
\\Fullwidth numbers: 1234567890
|
||||
\\Fullwidth letters: ABCDEFG abcdefg
|
||||
\\Fullwidth punctuation: !@#$%^&*()
|
||||
\\Half-width (normal): ABC 123
|
||||
\\Full-width (double): ABC 123
|
||||
\\Punctuation: 。,、;:「」『』
|
||||
\\Symbols: ○●□■△▲☆★◇◆
|
||||
\\
|
||||
\\ recommended fonts for terminals with no nerdfont fallback support (e.g. flow-gui):
|
||||
\\
|
||||
\\ "IosevkaTerm Nerd Font" => https://github.com/ryanoasis/nerd-fonts/releases/download/v3.3.0/IosevkaTerm.zip
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -608,8 +617,10 @@ const cmds = struct {
|
|||
|
||||
pub fn delete_buffer(self: *Self, ctx: Ctx) Result {
|
||||
var file_path: []const u8 = undefined;
|
||||
if (!(ctx.args.match(.{tp.extract(&file_path)}) catch false))
|
||||
return error.InvalidDeleteBufferArgument;
|
||||
if (!(ctx.args.match(.{tp.extract(&file_path)}) catch false)) {
|
||||
const editor = self.get_active_editor() orelse return error.InvalidDeleteBufferArgument;
|
||||
file_path = editor.file_path orelse return error.InvalidDeleteBufferArgument;
|
||||
}
|
||||
const buffer = self.buffer_manager.get_buffer_for_file(file_path) orelse return;
|
||||
if (buffer.is_dirty())
|
||||
return tp.exit("unsaved changes");
|
||||
|
|
|
|||
|
|
@ -1,17 +1,82 @@
|
|||
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 = .{},
|
||||
part: enum { row, col } = .row,
|
||||
};
|
||||
pub const Separator = ':';
|
||||
|
||||
pub fn name(_: *Type) []const u8 {
|
||||
return "#goto";
|
||||
}
|
||||
|
||||
pub fn start(_: *Type) usize {
|
||||
const editor = tui.get_active_editor() orelse return 1;
|
||||
return editor.get_primary().cursor.row + 1;
|
||||
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 {
|
||||
const part = if (self.input) |input| input.part else .row;
|
||||
switch (part) {
|
||||
.row => switch (digit) {
|
||||
0 => {
|
||||
if (self.input) |*input| input.cursor.row = input.cursor.row * 10;
|
||||
},
|
||||
1...9 => {
|
||||
if (self.input) |*input| {
|
||||
input.cursor.row = input.cursor.row * 10 + digit;
|
||||
} else {
|
||||
self.input = .{ .cursor = .{ .row = digit } };
|
||||
}
|
||||
},
|
||||
else => unreachable,
|
||||
},
|
||||
.col => if (self.input) |*input| {
|
||||
input.cursor.col = input.cursor.col * 10 + digit;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_separator(self: *Type) void {
|
||||
if (self.input) |*input| switch (input.part) {
|
||||
.row => input.part = .col,
|
||||
else => {},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn delete(self: *Type, input: *ValueType) void {
|
||||
switch (input.part) {
|
||||
.row => {
|
||||
const newval = if (input.cursor.row < 10) 0 else input.cursor.row / 10;
|
||||
if (newval == 0) self.input = null else input.cursor.row = newval;
|
||||
},
|
||||
.col => {
|
||||
const newval = if (input.cursor.col < 10) 0 else input.cursor.col / 10;
|
||||
if (newval == 0) {
|
||||
input.part = .row;
|
||||
input.cursor.col = 0;
|
||||
} else input.cursor.col = newval;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_value(_: *Type, input: ?ValueType, buf: []u8) []const u8 {
|
||||
return if (input) |value| blk: {
|
||||
switch (value.part) {
|
||||
.row => break :blk fmt.bufPrint(buf, "{d}", .{value.cursor.row}) catch "",
|
||||
.col => if (value.cursor.col == 0)
|
||||
break :blk fmt.bufPrint(buf, "{d}:", .{value.cursor.row}) catch ""
|
||||
else
|
||||
break :blk fmt.bufPrint(buf, "{d}:{d}", .{ value.cursor.row, value.cursor.col }) catch "",
|
||||
}
|
||||
} else "";
|
||||
}
|
||||
|
||||
pub const preview = goto;
|
||||
|
|
@ -19,5 +84,9 @@ pub const apply = goto;
|
|||
pub const cancel = goto;
|
||||
|
||||
fn goto(self: *Type, _: command.Context) void {
|
||||
command.executeName("goto_line", command.fmt(.{self.input orelse self.start})) catch {};
|
||||
send_goto(if (self.input) |input| input.cursor else self.start.cursor);
|
||||
}
|
||||
|
||||
fn send_goto(cursor: Cursor) void {
|
||||
command.executeName("goto_line_and_column", command.fmt(.{ cursor.row, cursor.col })) catch {};
|
||||
}
|
||||
|
|
|
|||
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 {};
|
||||
}
|
||||
|
|
@ -18,10 +18,12 @@ pub fn Create(options: type) type {
|
|||
|
||||
const Commands = command.Collection(cmds);
|
||||
|
||||
const ValueType = if (@hasDecl(options, "ValueType")) options.ValueType else usize;
|
||||
|
||||
allocator: Allocator,
|
||||
buf: [30]u8 = undefined,
|
||||
input: ?usize = null,
|
||||
start: usize,
|
||||
input: ?ValueType = null,
|
||||
start: ValueType,
|
||||
ctx: command.Context,
|
||||
commands: Commands = undefined,
|
||||
|
||||
|
|
@ -31,7 +33,7 @@ pub fn Create(options: type) type {
|
|||
self.* = .{
|
||||
.allocator = allocator,
|
||||
.ctx = .{ .args = try ctx.args.clone(allocator) },
|
||||
.start = 0,
|
||||
.start = if (@hasDecl(options, "ValueType")) ValueType{} else 0,
|
||||
};
|
||||
self.start = options.start(self);
|
||||
try self.commands.init(self);
|
||||
|
|
@ -55,27 +57,42 @@ pub fn Create(options: type) type {
|
|||
|
||||
fn update_mini_mode_text(self: *Self) void {
|
||||
if (tui.mini_mode()) |mini_mode| {
|
||||
mini_mode.text = if (self.input) |linenum|
|
||||
(fmt.bufPrint(&self.buf, "{d}", .{linenum}) catch "")
|
||||
else
|
||||
"";
|
||||
if (@hasDecl(options, "format_value")) {
|
||||
mini_mode.text = options.format_value(self, self.input, &self.buf);
|
||||
} else {
|
||||
mini_mode.text = if (self.input) |linenum|
|
||||
(fmt.bufPrint(&self.buf, "{d}", .{linenum}) catch "")
|
||||
else
|
||||
"";
|
||||
}
|
||||
mini_mode.cursor = tui.egc_chunk_width(mini_mode.text, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_char(self: *Self, char: u8) void {
|
||||
switch (char) {
|
||||
'0' => {
|
||||
if (self.input) |linenum| self.input = linenum * 10;
|
||||
},
|
||||
'1'...'9' => {
|
||||
const digit: usize = @intCast(char - '0');
|
||||
self.input = if (self.input) |x| x * 10 + digit else digit;
|
||||
},
|
||||
else => {},
|
||||
const process_digit_ = if (@hasDecl(options, "process_digit")) options.process_digit else process_digit;
|
||||
if (@hasDecl(options, "Separator")) {
|
||||
switch (char) {
|
||||
'0'...'9' => process_digit_(self, @intCast(char - '0')),
|
||||
options.Separator => options.process_separator(self),
|
||||
else => {},
|
||||
}
|
||||
} else {
|
||||
switch (char) {
|
||||
'0'...'9' => process_digit_(self, @intCast(char - '0')),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_digit(self: *Self, digit: u8) void {
|
||||
self.input = switch (digit) {
|
||||
0 => if (self.input) |value| value * 10 else 0,
|
||||
1...9 => if (self.input) |x| x * 10 + digit else digit,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
fn insert_bytes(self: *Self, bytes: []const u8) void {
|
||||
for (bytes) |c| self.insert_char(c);
|
||||
}
|
||||
|
|
@ -101,9 +118,13 @@ pub fn Create(options: type) type {
|
|||
pub const mini_mode_cancel_meta: Meta = .{ .description = "Cancel input" };
|
||||
|
||||
pub fn mini_mode_delete_backwards(self: *Self, _: Ctx) Result {
|
||||
if (self.input) |linenum| {
|
||||
const newval = if (linenum < 10) 0 else linenum / 10;
|
||||
self.input = if (newval == 0) null else newval;
|
||||
if (self.input) |*input| {
|
||||
if (@hasDecl(options, "delete")) {
|
||||
options.delete(self, input);
|
||||
} else {
|
||||
const newval = if (input.* < 10) 0 else input.* / 10;
|
||||
self.input = if (newval == 0) null else newval;
|
||||
}
|
||||
self.update_mini_mode_text();
|
||||
options.preview(self, self.ctx);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,9 +37,9 @@ pub fn select(self: *Type) void {
|
|||
var buf = std.ArrayList(u8).init(self.allocator);
|
||||
defer buf.deinit();
|
||||
const file_path = project_manager.expand_home(&buf, self.file_path.items);
|
||||
command.executeName("exit_mini_mode", .{}) catch {};
|
||||
if (root.is_directory(file_path))
|
||||
tp.self_pid().send(.{ "cmd", "change_project", .{file_path} }) catch {}
|
||||
else if (file_path.len > 0)
|
||||
tp.self_pid().send(.{ "cmd", "navigate", .{ .file = file_path } }) catch {};
|
||||
command.executeName("exit_mini_mode", .{}) catch {};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ fn select(menu: **Type.MenuState, button: *Type.ButtonState) void {
|
|||
}
|
||||
|
||||
const CompletionItemKind = enum(u8) {
|
||||
None = 0,
|
||||
Text = 1,
|
||||
Method = 2,
|
||||
Function = 3,
|
||||
|
|
@ -146,6 +147,7 @@ const CompletionItemKind = enum(u8) {
|
|||
|
||||
fn kind_icon(kind: CompletionItemKind) []const u8 {
|
||||
return switch (kind) {
|
||||
.None => " ",
|
||||
.Text => "",
|
||||
.Method => "",
|
||||
.Function => "",
|
||||
|
|
|
|||
|
|
@ -51,6 +51,31 @@ const cmds_ = struct {
|
|||
}
|
||||
pub const @"e!_meta": Meta = .{ .description = "e! (force reload current file)" };
|
||||
|
||||
pub fn bd(_: *void, _: Ctx) Result {
|
||||
try cmd("close_file", .{});
|
||||
}
|
||||
pub const bd_meta: Meta = .{ .description = "bd (Close file)" };
|
||||
|
||||
pub fn bw(_: *void, _: Ctx) Result {
|
||||
try cmd("delete_buffer", .{});
|
||||
}
|
||||
pub const bw_meta: Meta = .{ .description = "bw (Delete buffer)" };
|
||||
|
||||
pub fn bnext(_: *void, _: Ctx) Result {
|
||||
try cmd("next_tab", .{});
|
||||
}
|
||||
pub const bnext_meta: Meta = .{ .description = "bnext (Next buffer/tab)" };
|
||||
|
||||
pub fn bprevious(_: *void, _: Ctx) Result {
|
||||
try cmd("next_tab", .{});
|
||||
}
|
||||
pub const bprevious_meta: Meta = .{ .description = "bprevious (Previous buffer/tab)" };
|
||||
|
||||
pub fn ls(_: *void, _: Ctx) Result {
|
||||
try cmd("switch_buffers", .{});
|
||||
}
|
||||
pub const ls_meta: Meta = .{ .description = "ls (List/switch buffers)" };
|
||||
|
||||
pub fn move_begin_or_add_integer_argument_zero(_: *void, _: Ctx) Result {
|
||||
return if (@import("keybind").current_integer_argument()) |_|
|
||||
command.executeName("add_integer_argument_digit", command.fmt(.{0}))
|
||||
|
|
|
|||
|
|
@ -1034,9 +1034,31 @@ const cmds = struct {
|
|||
pub const find_in_files_meta: Meta = .{ .description = "Find in files" };
|
||||
|
||||
pub fn goto(self: *Self, ctx: Ctx) Result {
|
||||
return enter_mini_mode(self, @import("mode/mini/goto.zig"), ctx);
|
||||
var line: usize = undefined;
|
||||
var column: usize = undefined;
|
||||
return if (try ctx.args.match(.{tp.extract(&line)}))
|
||||
command.executeName("goto_line", command.fmt(.{line}))
|
||||
else if (try ctx.args.match(.{ tp.extract(&line), tp.extract(&column) }))
|
||||
command.executeName("goto_line_and_column", command.fmt(.{ line, column }))
|
||||
else
|
||||
enter_mini_mode(self, @import("mode/mini/goto.zig"), ctx);
|
||||
}
|
||||
pub const goto_meta: Meta = .{ .description = "Goto line" };
|
||||
pub const goto_meta: Meta = .{
|
||||
.description = "Goto line",
|
||||
.arguments = &.{ .integer, .integer },
|
||||
};
|
||||
|
||||
pub fn goto_offset(self: *Self, ctx: Ctx) Result {
|
||||
var offset: usize = undefined;
|
||||
return if (try ctx.args.match(.{tp.extract(&offset)}))
|
||||
command.executeName("goto_byte_offset", command.fmt(.{offset}))
|
||||
else
|
||||
enter_mini_mode(self, @import("mode/mini/goto_offset.zig"), ctx);
|
||||
}
|
||||
pub const goto_offset_meta: Meta = .{
|
||||
.description = "Goto byte offset",
|
||||
.arguments = &.{.integer},
|
||||
};
|
||||
|
||||
pub fn move_to_char(self: *Self, ctx: Ctx) Result {
|
||||
return enter_mini_mode(self, @import("mode/mini/move_to_char.zig"), ctx);
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ pub fn render(
|
|||
self: *const DwriteRenderer,
|
||||
font: Font,
|
||||
utf8: []const u8,
|
||||
double_width: bool,
|
||||
) void {
|
||||
var utf16_buf: [10]u16 = undefined;
|
||||
const utf16_len = std.unicode.utf8ToUtf16Le(&utf16_buf, utf8) catch unreachable;
|
||||
|
|
@ -85,7 +86,10 @@ pub fn render(
|
|||
const rect: win32.D2D_RECT_F = .{
|
||||
.left = 0,
|
||||
.top = 0,
|
||||
.right = @floatFromInt(font.cell_size.x),
|
||||
.right = if (double_width)
|
||||
@as(f32, @floatFromInt(font.cell_size.x)) * 2
|
||||
else
|
||||
@as(f32, @floatFromInt(font.cell_size.x)),
|
||||
.bottom = @floatFromInt(font.cell_size.y),
|
||||
};
|
||||
self.render_target.BeginDraw();
|
||||
|
|
@ -96,7 +100,7 @@ pub fn render(
|
|||
self.render_target.DrawText(
|
||||
@ptrCast(utf16.ptr),
|
||||
@intCast(utf16.len),
|
||||
font.text_format,
|
||||
if (double_width) font.text_format_double else font.text_format_single,
|
||||
&rect,
|
||||
&self.white_brush.ID2D1Brush,
|
||||
.{},
|
||||
|
|
|
|||
|
|
@ -5,9 +5,15 @@ const Node = struct {
|
|||
prev: ?u32,
|
||||
next: ?u32,
|
||||
codepoint: ?u21,
|
||||
right_half: ?bool,
|
||||
};
|
||||
|
||||
map: std.AutoHashMapUnmanaged(u21, u32) = .{},
|
||||
const MapKey = struct {
|
||||
codepoint: u21,
|
||||
right_half: bool,
|
||||
};
|
||||
|
||||
map: std.AutoHashMapUnmanaged(MapKey, u32) = .{},
|
||||
nodes: []Node,
|
||||
front: u32,
|
||||
back: u32,
|
||||
|
|
@ -25,13 +31,14 @@ pub fn init(allocator: std.mem.Allocator, capacity: u32) error{OutOfMemory}!Glyp
|
|||
|
||||
pub fn clearRetainingCapacity(self: *GlyphIndexCache) void {
|
||||
self.map.clearRetainingCapacity();
|
||||
self.nodes[0] = .{ .prev = null, .next = 1, .codepoint = null };
|
||||
self.nodes[self.nodes.len - 1] = .{ .prev = @intCast(self.nodes.len - 2), .next = null, .codepoint = null };
|
||||
self.nodes[0] = .{ .prev = null, .next = 1, .codepoint = null, .right_half = null };
|
||||
self.nodes[self.nodes.len - 1] = .{ .prev = @intCast(self.nodes.len - 2), .next = null, .codepoint = null, .right_half = null };
|
||||
for (self.nodes[1 .. self.nodes.len - 1], 1..) |*node, index| {
|
||||
node.* = .{
|
||||
.prev = @intCast(index - 1),
|
||||
.next = @intCast(index + 1),
|
||||
.codepoint = null,
|
||||
.right_half = null,
|
||||
};
|
||||
}
|
||||
self.front = 0;
|
||||
|
|
@ -51,12 +58,12 @@ const Reserved = struct {
|
|||
index: u32,
|
||||
replaced: ?u21,
|
||||
};
|
||||
pub fn reserve(self: *GlyphIndexCache, allocator: std.mem.Allocator, codepoint: u21) error{OutOfMemory}!union(enum) {
|
||||
pub fn reserve(self: *GlyphIndexCache, allocator: std.mem.Allocator, codepoint: u21, right_half: bool) error{OutOfMemory}!union(enum) {
|
||||
newly_reserved: Reserved,
|
||||
already_reserved: u32,
|
||||
} {
|
||||
{
|
||||
const entry = try self.map.getOrPut(allocator, codepoint);
|
||||
const entry = try self.map.getOrPut(allocator, .{ .codepoint = codepoint, .right_half = right_half });
|
||||
if (entry.found_existing) {
|
||||
self.moveToBack(entry.value_ptr.*);
|
||||
return .{ .already_reserved = entry.value_ptr.* };
|
||||
|
|
@ -69,7 +76,7 @@ pub fn reserve(self: *GlyphIndexCache, allocator: std.mem.Allocator, codepoint:
|
|||
const replaced = self.nodes[self.front].codepoint;
|
||||
self.nodes[self.front].codepoint = codepoint;
|
||||
if (replaced) |r| {
|
||||
const removed = self.map.remove(r);
|
||||
const removed = self.map.remove(.{ .codepoint = r, .right_half = self.nodes[self.front].right_half orelse false });
|
||||
std.debug.assert(removed);
|
||||
}
|
||||
const save_front = self.front;
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ pub const WindowState = struct {
|
|||
}
|
||||
|
||||
// TODO: this should take a utf8 graphme instead
|
||||
pub fn generateGlyph(state: *WindowState, font: Font, codepoint: u21) u32 {
|
||||
pub fn generateGlyph(state: *WindowState, font: Font, codepoint: u21, kind: enum { single, left, right }) u32 {
|
||||
// for now we'll just use 1 texture and leverage the entire thing
|
||||
const texture_cell_count: XY(u16) = getD3d11TextureMaxCellCount(font.cell_size);
|
||||
const texture_cell_count_total: u32 =
|
||||
|
|
@ -184,16 +184,27 @@ pub const WindowState = struct {
|
|||
break :blk &(state.glyph_index_cache.?);
|
||||
};
|
||||
|
||||
const right_half: bool = switch (kind) {
|
||||
.single, .left => false,
|
||||
.right => true,
|
||||
};
|
||||
|
||||
switch (glyph_index_cache.reserve(
|
||||
global.glyph_cache_arena.allocator(),
|
||||
codepoint,
|
||||
right_half,
|
||||
) catch |e| oom(e)) {
|
||||
.newly_reserved => |reserved| {
|
||||
// var render_success = false;
|
||||
// defer if (!render_success) state.glyph_index_cache.remove(reserved.index);
|
||||
const pos: XY(u16) = cellPosFromIndex(reserved.index, texture_cell_count.x);
|
||||
const coord = coordFromCellPos(font.cell_size, pos);
|
||||
const staging = global.staging_texture.update(font.cell_size);
|
||||
const staging_size: XY(u16) = .{
|
||||
// twice the width to handle double-wide glyphs
|
||||
.x = font.cell_size.x * 2,
|
||||
.y = font.cell_size.y,
|
||||
};
|
||||
const staging = global.staging_texture.update(staging_size);
|
||||
var utf8_buf: [7]u8 = undefined;
|
||||
const utf8_len: u3 = std.unicode.utf8Encode(codepoint, &utf8_buf) catch |e| std.debug.panic(
|
||||
"todo: handle invalid codepoint {} (0x{0x}) ({s})",
|
||||
|
|
@ -202,12 +213,16 @@ pub const WindowState = struct {
|
|||
staging.text_renderer.render(
|
||||
font,
|
||||
utf8_buf[0..utf8_len],
|
||||
switch (kind) {
|
||||
.single => false,
|
||||
.left, .right => true,
|
||||
},
|
||||
);
|
||||
const box: win32.D3D11_BOX = .{
|
||||
.left = 0,
|
||||
.left = if (right_half) font.cell_size.x else 0,
|
||||
.top = 0,
|
||||
.front = 0,
|
||||
.right = font.cell_size.x,
|
||||
.right = if (right_half) font.cell_size.x * 2 else font.cell_size.x,
|
||||
.bottom = font.cell_size.y,
|
||||
.back = 1,
|
||||
};
|
||||
|
|
@ -289,7 +304,7 @@ pub fn paint(
|
|||
}
|
||||
|
||||
const copy_col_count: u16 = @min(col_count, shader_col_count);
|
||||
const blank_space_glyph_index = state.generateGlyph(font, ' ');
|
||||
const blank_space_glyph_index = state.generateGlyph(font, ' ', .single);
|
||||
|
||||
const cell_count: u32 = @as(u32, shader_col_count) * @as(u32, shader_row_count);
|
||||
state.shader_cells.updateCount(cell_count);
|
||||
|
|
|
|||
|
|
@ -23,11 +23,13 @@ pub fn init() void {
|
|||
}
|
||||
|
||||
pub const Font = struct {
|
||||
text_format: *win32.IDWriteTextFormat,
|
||||
text_format_single: *win32.IDWriteTextFormat,
|
||||
text_format_double: *win32.IDWriteTextFormat,
|
||||
cell_size: XY(u16),
|
||||
|
||||
pub fn init(dpi: u32, size: f32, face: *const FontFace) Font {
|
||||
var text_format: *win32.IDWriteTextFormat = undefined;
|
||||
var text_format_single: *win32.IDWriteTextFormat = undefined;
|
||||
|
||||
{
|
||||
const hr = global.dwrite_factory.CreateTextFormat(
|
||||
face.ptr(),
|
||||
|
|
@ -37,14 +39,43 @@ pub const Font = struct {
|
|||
.NORMAL, // stretch
|
||||
win32.scaleDpi(f32, size, dpi),
|
||||
win32.L(""), // locale
|
||||
&text_format,
|
||||
&text_format_single,
|
||||
);
|
||||
if (hr < 0) std.debug.panic(
|
||||
"CreateTextFormat '{}' height {d} failed, hresult=0x{x}",
|
||||
.{ std.unicode.fmtUtf16Le(face.slice()), size, @as(u32, @bitCast(hr)) },
|
||||
);
|
||||
}
|
||||
errdefer _ = text_format.IUnknown.Release();
|
||||
errdefer _ = text_format_single.IUnknown.Release();
|
||||
|
||||
var text_format_double: *win32.IDWriteTextFormat = undefined;
|
||||
{
|
||||
const hr = global.dwrite_factory.CreateTextFormat(
|
||||
face.ptr(),
|
||||
null,
|
||||
.NORMAL, //weight
|
||||
.NORMAL, // style
|
||||
.NORMAL, // stretch
|
||||
win32.scaleDpi(f32, size, dpi),
|
||||
win32.L(""), // locale
|
||||
&text_format_double,
|
||||
);
|
||||
if (hr < 0) std.debug.panic(
|
||||
"CreateTextFormat '{}' height {d} failed, hresult=0x{x}",
|
||||
.{ std.unicode.fmtUtf16Le(face.slice()), size, @as(u32, @bitCast(hr)) },
|
||||
);
|
||||
}
|
||||
errdefer _ = text_format_double.IUnknown.Release();
|
||||
|
||||
{
|
||||
const hr = text_format_double.SetTextAlignment(win32.DWRITE_TEXT_ALIGNMENT_CENTER);
|
||||
if (hr < 0) fatalHr("SetTextAlignment", hr);
|
||||
}
|
||||
|
||||
{
|
||||
const hr = text_format_double.SetParagraphAlignment(win32.DWRITE_PARAGRAPH_ALIGNMENT_CENTER);
|
||||
if (hr < 0) fatalHr("SetParagraphAlignment", hr);
|
||||
}
|
||||
|
||||
const cell_size: XY(u16) = blk: {
|
||||
var text_layout: *win32.IDWriteTextLayout = undefined;
|
||||
|
|
@ -52,7 +83,7 @@ pub const Font = struct {
|
|||
const hr = global.dwrite_factory.CreateTextLayout(
|
||||
win32.L("█"),
|
||||
1,
|
||||
text_format,
|
||||
text_format_single,
|
||||
std.math.floatMax(f32),
|
||||
std.math.floatMax(f32),
|
||||
&text_layout,
|
||||
|
|
@ -73,13 +104,15 @@ pub const Font = struct {
|
|||
};
|
||||
|
||||
return .{
|
||||
.text_format = text_format,
|
||||
.text_format_single = text_format_single,
|
||||
.text_format_double = text_format_double,
|
||||
.cell_size = cell_size,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Font) void {
|
||||
_ = self.text_format.IUnknown.Release();
|
||||
_ = self.text_format_single.IUnknown.Release();
|
||||
_ = self.text_format_double.IUnknown.Release();
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1081,19 +1081,31 @@ fn WndProc(
|
|||
global.render_cells_arena.allocator(),
|
||||
global.screen.buf.len,
|
||||
) catch |e| oom(e);
|
||||
var prev_width: usize = 1;
|
||||
var prev_cell: render.Cell = undefined;
|
||||
var prev_codepoint: u21 = undefined;
|
||||
for (global.screen.buf, global.render_cells.items) |*screen_cell, *render_cell| {
|
||||
const codepoint = if (std.unicode.utf8ValidateSlice(screen_cell.char.grapheme))
|
||||
const width = screen_cell.char.width;
|
||||
// temporary workaround, ignore multi-codepoint graphemes
|
||||
const codepoint = if (screen_cell.char.grapheme.len > 4)
|
||||
std.unicode.replacement_character
|
||||
else if (std.unicode.utf8ValidateSlice(screen_cell.char.grapheme))
|
||||
std.unicode.wtf8Decode(screen_cell.char.grapheme) catch std.unicode.replacement_character
|
||||
else
|
||||
std.unicode.replacement_character;
|
||||
render_cell.* = .{
|
||||
.glyph_index = state.render_state.generateGlyph(
|
||||
font,
|
||||
codepoint,
|
||||
),
|
||||
.background = renderColorFromVaxis(screen_cell.style.bg),
|
||||
.foreground = renderColorFromVaxis(screen_cell.style.fg),
|
||||
};
|
||||
if (prev_width > 1) {
|
||||
render_cell.* = prev_cell;
|
||||
render_cell.glyph_index = state.render_state.generateGlyph(font, prev_codepoint, .right);
|
||||
} else {
|
||||
render_cell.* = .{
|
||||
.glyph_index = state.render_state.generateGlyph(font, codepoint, if (width == 1) .single else .left),
|
||||
.background = renderColorFromVaxis(screen_cell.style.bg),
|
||||
.foreground = renderColorFromVaxis(screen_cell.style.fg),
|
||||
};
|
||||
}
|
||||
prev_width = width;
|
||||
prev_cell = render_cell.*;
|
||||
prev_codepoint = codepoint;
|
||||
}
|
||||
render.paint(
|
||||
&state.render_state,
|
||||
|
|
|
|||
|
|
@ -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