diff options
-rw-r--r-- | src/Buffer.zig | 79 | ||||
-rw-r--r-- | src/Selection.zig | 367 | ||||
-rw-r--r-- | src/main.janet | 20 | ||||
-rw-r--r-- | src/main.zig | 13 |
4 files changed, 314 insertions, 165 deletions
diff --git a/src/Buffer.zig b/src/Buffer.zig index faee5c4..b1aacaf 100644 --- a/src/Buffer.zig +++ b/src/Buffer.zig @@ -27,7 +27,8 @@ const initial_gap_size = std.atomic.cache_line; const Buffer = @This(); /// File content with gap, owned by Janet runtime. -data: []u8, +data: []const u8, +file: []const u8, gap_position: u32 = 0, gap_size: u32 = initial_gap_size, parser: *Parser, @@ -53,10 +54,11 @@ fn read(payload: ?*anyopaque, byte_index: u32, _: Point, } } -pub fn open(text: []u8) !Buffer { +pub fn open(text: []const u8, file: []const u8) !Buffer { const data = gap: { + const unsafe_ptr: *anyopaque = @constCast(@ptrCast(text.ptr)); const n: i32 = @intCast(text.len); - const buffer = janet.pointerBufferUnsafe(@ptrCast(text.ptr), n, n); + const buffer = janet.pointerBufferUnsafe(unsafe_ptr, n, n); const capacity: usize = @intCast(n + initial_gap_size); if (janet.realloc(buffer.*.data, capacity)) |data| { janet.gcpressure(initial_gap_size); @@ -78,6 +80,7 @@ pub fn open(text: []u8) !Buffer { try parser.setLanguage(language); var result = Buffer{ .data = data, + .file = file, .parser = parser, .tree = undefined, .selection = undefined, @@ -86,10 +89,8 @@ pub fn open(text: []u8) !Buffer { .payload = &result, .read = Buffer.read, }, null).?; - result.selection = .{ - .head = .{ .node = result.tree.rootNode() }, - .tail = .{ .node = result.tree.rootNode() }, - }; + const span0 = Selection.Unit{ .span = .{ .tree = result.tree } }; + result.selection = .{ .head = span0, .tail = span0 }; return result; } @@ -99,11 +100,37 @@ pub fn close(buffer: Buffer) void { buffer.parser.destroy(); } +pub fn length(buffer: Buffer) u32 { + return @intCast(buffer.data.len - buffer.gap_size); +} + +pub fn graphemeAt(buffer: Buffer, n: u32) Grapheme { + const text = if (n < buffer.gap_position) + buffer.data[n..buffer.gap_position] + else + buffer.data[buffer.gap_size..][n..]; + var iterator = graphemes.iterator(text); + return .{ .offset = n, .len = iterator.peek().?.len }; +} + +pub fn graphemeBefore(buffer: Buffer, n: u32) ?Grapheme { + if (n == 0) + return null; + const text = if (n <= buffer.gap_position) + buffer.data[0..n] + else + buffer.data[buffer.gap_size..][buffer.gap_position..n]; + var iterator = graphemes.reverseIterator(text); + const len = iterator.peek().?.len; + return .{ .offset = n - len, .len = len }; +} + fn skipLines(text: []const u8, from: u32, n: u32) union(enum) { done: u32, left: u32, } { - assert(n > 0); + if (n == 0) + return .{ .done = from }; var lines: u32 = 0; for (text[from..], 1..) |c, i| if (c == '\n') { @@ -138,20 +165,28 @@ pub fn iterate(buffer: Buffer) struct { } else return null; } } { - const root_node = buffer.tree.rootNode(); const point = Point{ .row = buffer.scroll_row, .column = 0 }; - const node = root_node.descendantForPointRange(point, point).?; - const start_point = node.startPoint(); - assert(start_point.row < point.row or eql(start_point, point)); - - const start_byte = node.startByte(); - const start = if (eql(start_point, point)) - start_byte - else if (start_byte >= buffer.gap_position) - skipLines(buffer.data[buffer.gap_size..], start_byte, - point.row - start_point.row).done - else switch (skipLines(buffer.data[0..buffer.gap_position], start_byte, - point.row - start_point.row)) { + const root_node = buffer.tree.rootNode(); + const root_start = root_node.startPoint(); + const start = if (root_start.row < point.row + or eql(root_start, point)) skip: { + const node = root_node.descendantForPointRange(point, point).?; + const start_point = node.startPoint(); + const start_byte = node.startByte(); + if (eql(start_point, point)) + break :skip start_byte; + assert(start_point.row < point.row); + break :skip if (start_byte >= buffer.gap_position) + skipLines(buffer.data[buffer.gap_size..], + start_byte, point.row - start_point.row).done + else switch (skipLines(buffer.data[0..buffer.gap_position], + start_byte, point.row - start_point.row)) { + .done => |offset| offset, + .left => |left| skipLines(buffer.data[buffer.gap_size..], + buffer.gap_position, left).done, + }; + } else switch (skipLines(buffer.data[0..buffer.gap_position], 0, + point.row)) { .done => |offset| offset, .left => |left| skipLines(buffer.data[buffer.gap_size..], buffer.gap_position, left).done, @@ -172,7 +207,7 @@ pub fn iterate(buffer: Buffer) struct { .graphemes_iterator = graphemes.iterator(buffer.data[offset..]), .data = undefined, .offset = start, - .before_gap = true, + .before_gap = false, .gap_start = undefined, .gap_end = undefined, }; diff --git a/src/Selection.zig b/src/Selection.zig index 352af7a..5e01d32 100644 --- a/src/Selection.zig +++ b/src/Selection.zig @@ -2,6 +2,7 @@ // SPDX-FileCopyrightText: 2025 Nguyễn Gia Phong // SPDX-License-Identifier: GPL-3.0-or-later +const assert = std.debug.assert; const eql = std.meta.eql; const order = std.math.order; const std = @import("std"); @@ -13,6 +14,7 @@ const tree_sitter = @import("tree-sitter"); const Grapheme = vaxis.Graphemes.Grapheme; const vaxis = @import("vaxis"); +const Buffer = @import("Buffer.zig"); const graphemes = &@import("root").graphemes; const Selection = @This(); @@ -37,25 +39,79 @@ pub fn inBody(selection: Selection, grapheme: Grapheme) bool { and grapheme.offset + grapheme.len <= selection.endByte()); } -fn graphemeAt(text: []const u8, index: u32) Grapheme { - var iterator = graphemes.iterator(text[index..]); - return .{ .offset = index, .len = iterator.peek().?.len }; -} - -fn graphemeBefore(text: []const u8, index: u32) Grapheme { - var iterator = graphemes.reverseIterator(text[0..index]); - return iterator.peek().?; -} - // TODO: nesting -const Span = struct { - start: u32, - end: u32, +pub const Span = struct { + start: u32 = 0, + end: u32 = 0, tree: *const Tree, - fn parent(unit: Span) Node { - const root_node = unit.tree.rootNode(); - return root_node.descendantForByteRange(unit.start, unit.end).?; + pub fn root(text_len: u32, tree: *const Tree) Span { + return .{ .start = 0, .end = text_len, .tree = tree }; + } + + fn parent(span: Span, text_len: u32) Unit { + const root_span = Span.root(text_len, span.tree); + if (eql(root_span, span)) + return .{ .span = span }; + const root_node = span.tree.rootNode(); + const root_node_start = root_node.startByte(); + const root_node_end = root_node.endByte(); + if (root_node_start == span.start and root_node_end == span.end) + return .{ .span = root_span }; + if (root_node_start > span.start) { + const first_span = Span{ + .start = 0, + .end = root_node_start, + .tree = span.tree, + }; + return .{ + .span = if (eql(first_span, span)) root_span else first_span + }; + } + if (root_node_end < span.end) { + const last_span = Span{ + .start = root_node_start, + .end = text_len, + .tree = span.tree, + }; + return .{ + .span = if (eql(last_span, span)) root_span else last_span + }; + } + const parent_node = root_node.descendantForByteRange(span.start, + span.end).?; + const parent_node_start = parent_node.startByte(); + assert(parent_node_start <= span.start); + const parent_node_end = parent_node.endByte(); + assert(parent_node_end >= span.end); + const parent_span = grow: { + if (parent_node.firstChildForByte(span.end)) |next_node| { + break :grow if (next_node.prevSibling()) |prev_node| Span{ + .start = prev_node.endByte(), + .end = next_node.startByte(), + .tree = span.tree, + } else Span{ + .start = parent_node_start, + .end = next_node.startByte(), + .tree = span.tree, + }; + } else { + const siblings = parent_node.childCount(); + if (siblings == 0) + return .{ .node = parent_node }; + const prev_node = parent_node.child(siblings - 1).?; + break :grow Span{ + .start = prev_node.endByte(), + .end = parent_node_end, + .tree = span.tree, + }; + } + }; + return if (eql(parent_span, span)) .{ + .node = parent_node + } else .{ + .span = parent_span + }; } }; @@ -71,51 +127,26 @@ const GraphemeUnit = struct { return unit.slice.offset + unit.slice.len; } - fn parent(unit: GraphemeUnit) Span { - const start = unit.startByte(); - const end = unit.endByte(); - const root_node = unit.tree.rootNode(); - const parent_node = root_node.descendantForByteRange(start, end).?; - if (parent_node.firstChildForByte(end)) |next_node| { - return if (next_node.prevSibling()) |prev_node| .{ - .start = prev_node.endByte(), - .end = next_node.startByte(), - .tree = unit.tree, - } else .{ - .start = parent_node.startByte(), - .end = next_node.startByte(), - .tree = unit.tree, - }; - } else { - const siblings = parent_node.childCount(); - if (siblings == 0) - return .{ - .start = parent_node.startByte(), - .end = parent_node.endByte(), - .tree = unit.tree, - }; - const prev_node = parent_node.child(siblings - 1).?; - return .{ - .start = prev_node.endByte(), - .end = parent_node.endByte(), - .tree = unit.tree, - }; - } + fn parent(unit: GraphemeUnit, text_len: u32) Unit { + return Span.parent(.{ + .start = unit.startByte(), + .end = unit.endByte(), + .tree = unit.tree, + }, text_len); } - fn at(unit: GraphemeUnit, text: []const u8, index: u32) GraphemeUnit { + fn at(unit: GraphemeUnit, buffer: Buffer, index: u32) GraphemeUnit { return .{ - .slice = graphemeAt(text, index), + .slice = buffer.graphemeAt(index), .tree = unit.tree, }; } - fn before(unit: GraphemeUnit, - text: []const u8, index: u32) GraphemeUnit { - return .{ - .slice = graphemeBefore(text, index), + fn before(unit: GraphemeUnit, buffer: Buffer, index: u32) ?GraphemeUnit { + return if (buffer.graphemeBefore(index)) |grapheme| .{ + .slice = grapheme, .tree = unit.tree, - }; + } else null; } }; @@ -141,103 +172,175 @@ pub const Unit = union(enum) { }; } - pub fn up(unit: Unit, text: []const u8) Unit { - switch (unit) { - .node => |node| return if (node.parent()) |parent_node| - if (parent_node.eql(node)) - .up(.{ .node = parent_node }, text) - else - .{ .node = parent_node } - else unit, - .span => |span| { - const parent_node = span.parent(); - return if (parent_node.startByte() == span.start - and parent_node.endByte() == span.end) - .up(.{ .node = parent_node }, text) - else - .{ .node = parent_node }; - }, - .grapheme => |grapheme| { - const parent_span = grapheme.parent(); - return if (parent_span.start == grapheme.startByte() - and parent_span.end == grapheme.endByte()) - .up(.{ .span = parent_span }, text) - else - .{ .span = parent_span }; - }, - } + fn tree(unit: Unit) *const Tree { + return switch (unit) { + .node => |node| node.tree, + .span => |span| span.tree, + .grapheme => |grapheme| grapheme.tree, + }; + } + + fn equal(unit: Unit, other: Unit) bool { + return (unit.startByte() == other.startByte() + and unit.endByte() == other.endByte()); + } + + pub fn up(unit: Unit, buffer: Buffer) Unit { + const parent = switch (unit) { + .node => |node| if (node.parent()) |parent| Unit{ + .node = parent + } else Span.parent(.{ + .start = node.startByte(), + .end = node.endByte(), + .tree = node.tree, + }, buffer.length()), + .span => |span| span.parent(buffer.length()), + .grapheme => |grapheme| grapheme.parent(buffer.length()), + }; + return if (!parent.equal(unit) or parent.equal(.{ + .span = .root(buffer.length(), unit.tree()) + })) parent else parent.up(buffer); } - pub fn right(unit: Unit, text: []const u8) Unit { + pub fn right(unit: Unit, buffer: Buffer) Unit { switch (unit) { - .node => |node| if (node.nextSibling()) |sibling| { + .node => |node| { const prev = node.endByte(); - const next = sibling.startByte(); - return switch (order(prev, next)) { - .lt => .{ - .span = .{ - .start = prev, - .end = next, - .tree = node.tree, - } - }, - .eq => .{ .node = sibling }, - .gt => unreachable, - }; + const root_node = node.tree.rootNode(); + if (root_node.eql(node)) { + const span = Span{ + .start = prev, + .end = buffer.length(), + .tree = node.tree, + }; + if (span.start < span.end) + return .{ .span = span }; + } else if (node.nextSibling()) |sibling| { + const next = sibling.startByte(); + return switch (order(prev, next)) { + .lt => .{ + .span = .{ + .start = prev, + .end = next, + .tree = node.tree, + } + }, + .eq => .{ .node = sibling }, + .gt => unreachable, + }; + } }, - .span => |span| { - const parent_node = unit.up(text).node; - if (parent_node.firstChildForByte(span.end)) |next_node| - return .{ .node = next_node }; + .span => |span| switch (unit.up(buffer)) { + .node => |parent_node| { + if (parent_node.firstChildForByte(span.end)) |next_node| + return .{ .node = next_node }; + }, + .span => |parent_span| { + const root_node = span.tree.rootNode(); + if (root_node.startByte() == span.end) + return .{ .node = root_node }; + const next_span = Span{ + .start = span.end, + .end = parent_span.end, + .tree = span.tree, + }; + if (next_span.start < next_span.end) + return .{ .span = next_span }; + }, + .grapheme => unreachable, }, .grapheme => |grapheme| { - const start = grapheme.endByte(); - const next_grapheme = grapheme.at(text, start); - if (eql(next_grapheme.parent(), grapheme.parent())) + const next_grapheme = grapheme.at(buffer, grapheme.endByte()); + const len = buffer.length(); + if (next_grapheme.parent(len).equal(grapheme.parent(len))) return .{ .grapheme = next_grapheme }; + return .right(.{ + .span = .{ + .start = grapheme.startByte(), + .end = grapheme.endByte(), + .tree = grapheme.tree, + } + }, buffer); }, } - return .up(unit, text); + return unit.up(buffer); } - pub fn left(unit: Unit, text: []const u8) Unit { + pub fn left(unit: Unit, buffer: Buffer) Unit { switch (unit) { - .node => |node| if (node.prevSibling()) |sibling| { - const prev = sibling.endByte(); + .node => |node| { const next = node.startByte(); - return switch (order(prev, next)) { - .lt => .{ - .span = .{ - .start = prev, - .end = next, - .tree = node.tree, - } - }, - .eq => .{ .node = sibling }, - .gt => unreachable, - }; - }, - .span => |span| { - const parent_node = unit.up(text).node; - if (parent_node.firstChildForByte(span.end)) |next_node| { - if (next_node.prevSibling()) |prev_node| - return .{ .node = prev_node }; - } else { - const siblings = parent_node.childCount(); - return .{ .node = parent_node.child(siblings - 1).? }; + const root_node = node.tree.rootNode(); + if (root_node.eql(node)) { + const span = Span{ + .start = 0, + .end = next, + .tree = node.tree, + }; + if (span.start < span.end) + return .{ .span = span }; + } else if (node.prevSibling()) |sibling| { + const prev = sibling.endByte(); + return switch (order(prev, next)) { + .lt => .{ + .span = .{ + .start = prev, + .end = next, + .tree = node.tree, + } + }, + .eq => .{ .node = sibling }, + .gt => unreachable, + }; } }, + .span => |span| switch (unit.up(buffer)) { + .node => |parent_node| { + if (parent_node.firstChildForByte(span.end)) |next_node| { + if (next_node.prevSibling()) |prev_node| + return .{ .node = prev_node }; + } else { + const siblings = parent_node.childCount(); + if (siblings > 0) + return .{ + .node = parent_node.child(siblings - 1).? + }; + } + }, + .span => |parent_span| { + const root_node = span.tree.rootNode(); + if (root_node.endByte() == span.start) + return .{ .node = root_node }; + const prev_span = Span{ + .start = span.start, + .end = parent_span.start, + .tree = span.tree, + }; + if (prev_span.start < prev_span.end) + return .{ .span = prev_span }; + }, + .grapheme => unreachable, + }, .grapheme => |grapheme| { - const end = grapheme.slice.offset; - const prev_grapheme = grapheme.before(text, end); - if (eql(prev_grapheme.parent(), grapheme.parent())) - return .{ .grapheme = prev_grapheme }; + const start = grapheme.startByte(); + if (grapheme.before(buffer, start)) |prev_grapheme| { + const len = buffer.length(); + if (prev_grapheme.parent(len).equal(grapheme.parent(len))) + return .{ .grapheme = prev_grapheme }; + } + return .left(.{ + .span = .{ + .start = start, + .end = grapheme.endByte(), + .tree = grapheme.tree, + } + }, buffer); }, } - return .up(unit, text); + return unit.up(buffer); } - pub fn down(unit: Unit, text: []const u8) Unit { + pub fn down(unit: Unit, buffer: Buffer) Unit { switch (unit) { .node => |node| { const start = node.startByte(); @@ -245,17 +348,17 @@ pub const Unit = union(enum) { // TODO: history return if (node.child(0)) |child| if (child.startByte() == start and child.endByte() == end) - .down(.{ .node = child }, text) + .down(.{ .node = child }, buffer) else .{ .node = child } else .down(.{ .span = .{ .start = start, .end = end, .tree = node.tree } - }, text); + }, buffer); }, .span => |span| return .{ .grapheme = .{ // TODO: history - .slice = graphemeAt(text, span.start), + .slice = buffer.graphemeAt(span.start), .tree = span.tree, } }, diff --git a/src/main.janet b/src/main.janet index 4034dc9..9a6f42b 100644 --- a/src/main.janet +++ b/src/main.janet @@ -4,24 +4,22 @@ (defn kay/select/move [buf direction] - (let [data (buf :data) - selection (buf :selection)] + (let [selection (buf :selection)] (struct ;(kvs buf) :selection (match selection {:head head :tail tail} (struct ;(kvs selection) - :head (direction head data) - :tail (direction tail data)))))) + :head (direction head buf) + :tail (direction tail buf)))))) (defn kay/select/head [buf direction] - (let [data (buf :data) - selection (buf :selection)] + (let [selection (buf :selection)] (struct ;(kvs buf) :selection (match selection {:head head :tail tail} (struct ;(kvs selection) - :head (direction head data) + :head (direction head buf) :tail tail))))) (defn kay/select/flip @@ -40,7 +38,7 @@ (:matches key (chr "c") {:ctrl true}) [[:quit] buf] (:matches key (chr "y") {}) (do (:yank kay/env buf) [[] buf]) - (:matches key (chr "i") {}) (do (:paste kay/env) + (:matches key (chr "i") {}) (do (:request-paste kay/env) [[] buf]) (:matches key (chr "p") {}) [[] (struct ;(kvs buf) :scroll-row (max 0 (dec (buf :scroll-row))))] @@ -69,6 +67,8 @@ (run @[(:next-event kay/env)] buf))) -(with [buf (kay/open (with [f (file/open (string kay/path))] - (buffer (:read f :all))))] +(with [buf (kay/open (if-with [f (file/open (string kay/path))] + (buffer (:read f :all)) + @"\n") + kay/path)] (run @[] buf)) diff --git a/src/main.zig b/src/main.zig index d26cefa..2a4c39d 100644 --- a/src/main.zig +++ b/src/main.zig @@ -14,16 +14,18 @@ const ns_per_s = std.time.ns_per_s; const smp_allocator = std.heap.smp_allocator; const std = @import("std"); +const CursorShape = vaxis.Cell.CursorShape; const Graphemes = vaxis.Graphemes; const Key = vaxis.Key; const Loop = vaxis.Loop; const Style = vaxis.Cell.Style; const Tty = vaxis.Tty; const Unicode = vaxis.Unicode; +const Vaxis = vaxis.Vaxis; const Window = vaxis.Window; const Winsize = vaxis.Winsize; const gwidth = vaxis.gwidth.gwidth; -const Vaxis = vaxis.Vaxis; +const ctlseqs = vaxis.ctlseqs; const vaxis = @import("vaxis"); pub const panic = vaxis.panic_handler; @@ -61,10 +63,16 @@ const Environment = struct { pub fn render(self: Environment, buffer: Buffer) !void { const window = self.vaxis.window(); window.clear(); + const sel_start = buffer.selection.startByte(); + window.screen.cursor_vis = sel_start == buffer.selection.endByte(); var col: u16 = 0; var row: u16 = 0; var graphemes_iter = buffer.iterate(); while (graphemes_iter.next()) |grapheme| { + if (window.screen.cursor_vis and grapheme.offset == sel_start) { + window.setCursorShape(.beam); + window.showCursor(col, row); + } const in_head = buffer.selection.inHead(grapheme); const in_body = buffer.selection.inBody(grapheme); const style = Style { @@ -135,6 +143,9 @@ pub fn main() !void { var tty = try Tty.init(); defer tty.deinit(); + defer tty.anyWriter().print(ctlseqs.cursor_shape, .{ + @intFromEnum(CursorShape.block), // FIXME: make configurable + }) catch {}; var vx = try vaxis.init(allocator, .{ .system_clipboard_allocator = allocator, }); |