diff options
-rw-r--r-- | build.zig.zon | 4 | ||||
-rw-r--r-- | src/Buffer.zig | 202 | ||||
-rw-r--r-- | src/Selection.zig (renamed from src/selection.zig) | 225 | ||||
-rw-r--r-- | src/Token.zig | 103 | ||||
-rw-r--r-- | src/main.janet | 77 | ||||
-rw-r--r-- | src/main.zig | 117 |
6 files changed, 429 insertions, 299 deletions
diff --git a/build.zig.zon b/build.zig.zon index f62d31f..c52be37 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -17,8 +17,8 @@ .hash = "vaxis-0.5.1-BWNV_CAVCQAmzxx2i2hBs5SvEupHwDbKHtE5VSOZwwtl", }, .zsanett = .{ - .url = "git+https://trong.loang.net/~cnx/zsanett#f5b2d6f3122b54fcdcb3bd6953420480bb9bc752", - .hash = "zsanett-0.0.0-1TMqaI7-AAD5R_t1Udvn92vPkwJnBQXnVzLgKMlJkOIB", + .url = "git+https://trong.loang.net/~cnx/zsanett#7d61d53d2e6c33b7bb847f608f43e2775fa3da9b", + .hash = "zsanett-0.0.0-1TMqaN_8AADxYf9AQY9cfZhx2uKrh8iEhn1TLwNKiN4d", }, }, .paths = .{ diff --git a/src/Buffer.zig b/src/Buffer.zig new file mode 100644 index 0000000..faee5c4 --- /dev/null +++ b/src/Buffer.zig @@ -0,0 +1,202 @@ +// Gap buffer +// 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 std = @import("std"); + +const Input = tree_sitter.Input; +const Parser = tree_sitter.Parser; +const Point = tree_sitter.Point; +const Tree = tree_sitter.Tree; +const tree_sitter = @import("tree-sitter"); + +const Grapheme = Graphemes.Grapheme; +const Graphemes = vaxis.Graphemes; +const vaxis = @import("vaxis"); + +const janet = zsanett.janet; +const zsanett = @import("zsanett"); + +const Selection = @import("Selection.zig"); +const languages = @import("languages"); +const graphemes = &@import("main.zig").graphemes; + +const initial_gap_size = std.atomic.cache_line; + +const Buffer = @This(); +/// File content with gap, owned by Janet runtime. +data: []u8, +gap_position: u32 = 0, +gap_size: u32 = initial_gap_size, +parser: *Parser, +tree: *Tree, // TODO: polyglot +scroll_row: u32 = 0, +selection: Selection, +paste_direction: enum { before, after } = .before, + +fn read(payload: ?*anyopaque, byte_index: u32, _: Point, + bytes_read: *u32) callconv(.C) [*c]const u8 { + const buffer: *Buffer = @alignCast(@ptrCast(payload.?)); + if (byte_index < buffer.gap_position) { + bytes_read.* = @intCast(buffer.gap_position - byte_index); + return buffer.data[byte_index..buffer.gap_position].ptr; + } + const index_with_gap = byte_index + buffer.gap_size; + if (index_with_gap < buffer.data.len) { + bytes_read.* = @intCast(buffer.data.len - index_with_gap); + return buffer.data[index_with_gap..].ptr; + } else { + bytes_read.* = 0; + return ""; + } +} + +pub fn open(text: []u8) !Buffer { + const data = gap: { + const n: i32 = @intCast(text.len); + const buffer = janet.pointerBufferUnsafe(@ptrCast(text.ptr), n, n); + const capacity: usize = @intCast(n + initial_gap_size); + if (janet.realloc(buffer.*.data, capacity)) |data| { + janet.gcpressure(initial_gap_size); + buffer.*.data = @ptrCast(data); + buffer.*.capacity = @intCast(capacity); + buffer.*.count = buffer.*.capacity; + // TODO: use @memmove in Zig 0.15 + var i = capacity; + while (i >= initial_gap_size) : (i -= 1) + buffer.*.data[i] = buffer.*.data[i - initial_gap_size]; + break :gap buffer.*.data[0..capacity]; + } else return error.OutOfMemory; + }; + + const parser = Parser.create(); + errdefer parser.destroy(); + const language = languages.c(); + errdefer language.destroy(); + try parser.setLanguage(language); + var result = Buffer{ + .data = data, + .parser = parser, + .tree = undefined, + .selection = undefined, + }; + result.tree = parser.parse(.{ + .payload = &result, + .read = Buffer.read, + }, null).?; + result.selection = .{ + .head = .{ .node = result.tree.rootNode() }, + .tail = .{ .node = result.tree.rootNode() }, + }; + return result; +} + +pub fn close(buffer: Buffer) void { + buffer.tree.getLanguage().destroy(); + buffer.tree.destroy(); + buffer.parser.destroy(); +} + +fn skipLines(text: []const u8, from: u32, n: u32) union(enum) { + done: u32, + left: u32, +} { + assert(n > 0); + var lines: u32 = 0; + for (text[from..], 1..) |c, i| + if (c == '\n') { + lines += 1; + if (lines == n) + return .{ .done = @intCast(from + i) }; + }; + return .{ .left = n - lines }; +} + +pub fn iterate(buffer: Buffer) struct { + const Iterator = @This(); + graphemes_iterator: Graphemes.Iterator, + data: []const u8, + offset: u32, + before_gap: bool, + gap_start: u32, + gap_end: u32, + + pub fn next(iterator: *Iterator) ?Grapheme { + if (iterator.graphemes_iterator.next()) |grapheme| { + return .{ + .offset = grapheme.offset + iterator.offset, + .len = grapheme.len, + }; + } else if (iterator.before_gap) { + const buffer_after_gap = iterator.data[iterator.gap_end..]; + iterator.graphemes_iterator = graphemes.iterator(buffer_after_gap); + iterator.offset = iterator.gap_start; + iterator.before_gap = false; + return iterator.next(); + } 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)) { + .done => |offset| offset, + .left => |left| skipLines(buffer.data[buffer.gap_size..], + buffer.gap_position, left).done, + }; + if (start < buffer.gap_position) { + const buffer_before_gap = buffer.data[start..buffer.gap_position]; + return .{ + .graphemes_iterator = graphemes.iterator(buffer_before_gap), + .data = buffer.data, + .offset = start, + .before_gap = true, + .gap_start = buffer.gap_position, + .gap_end = buffer.gap_position + buffer.gap_size, + }; + } + const offset = start + buffer.gap_size; + return .{ + .graphemes_iterator = graphemes.iterator(buffer.data[offset..]), + .data = undefined, + .offset = start, + .before_gap = true, + .gap_start = undefined, + .gap_end = undefined, + }; +} + +pub fn bytes(buffer: Buffer, grapheme: Grapheme) []const u8 { + if (grapheme.offset < buffer.gap_position) + return grapheme.bytes(buffer.data); + const start = grapheme.offset + buffer.gap_size; + const end = start + grapheme.len; + return buffer.data[start..end]; +} + +pub fn selectedBytes(buffer: Buffer) []const u8 { + const start = buffer.selection.startByte(); + const end = buffer.selection.endByte(); + if (end <= buffer.gap_position) + return buffer.data[start..end]; + if (start >= buffer.gap_position) + return buffer.data[buffer.gap_size..][start..end]; + unreachable; +} + +pub fn paste(buffer: Buffer, data: []const u8) Buffer { + _ = data; + return buffer; +} diff --git a/src/selection.zig b/src/Selection.zig index 44d537f..352af7a 100644 --- a/src/selection.zig +++ b/src/Selection.zig @@ -6,101 +6,124 @@ const eql = std.meta.eql; const order = std.math.order; const std = @import("std"); -const Grapheme = Graphemes.Grapheme; -const Graphemes = vaxis.Graphemes; const Node = tree_sitter.Node; const Tree = tree_sitter.Tree; const tree_sitter = @import("tree-sitter"); + +const Grapheme = vaxis.Graphemes.Grapheme; const vaxis = @import("vaxis"); -fn graphemeAt(graphemes: Graphemes, text: []const u8, index: u32) Grapheme { +const graphemes = &@import("root").graphemes; + +const Selection = @This(); +head: Unit, +tail: Unit, + +pub fn startByte(selection: Selection) u32 { + return @min(selection.head.startByte(), selection.tail.startByte()); +} + +pub fn endByte(selection: Selection) u32 { + return @max(selection.head.endByte(), selection.tail.endByte()); +} + +pub fn inHead(selection: Selection, grapheme: Grapheme) bool { + return selection.head.contains(grapheme); +} + +pub fn inBody(selection: Selection, grapheme: Grapheme) bool { + return (!selection.inHead(grapheme) + and selection.startByte() <= grapheme.offset + 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(graphemes: Graphemes, text: []const u8, index: u32) Grapheme { +fn graphemeBefore(text: []const u8, index: u32) Grapheme { var iterator = graphemes.reverseIterator(text[0..index]); return iterator.peek().?; } -// TODO: undo -pub const Unit = union(enum) { - node: Node, - span: Span, - grapheme: GraphemeUnit, - - // TODO: nesting - const Span = struct { - start: u32, - end: u32, - tree: *const Tree, +// TODO: nesting +const Span = struct { + start: u32, + end: u32, + tree: *const Tree, - fn parent(unit: Span) Node { - const root_node = unit.tree.rootNode(); - return root_node.descendantForByteRange(unit.start, unit.end).?; - } - }; + fn parent(unit: Span) Node { + const root_node = unit.tree.rootNode(); + return root_node.descendantForByteRange(unit.start, unit.end).?; + } +}; - const GraphemeUnit = struct { - slice: Grapheme, - tree: *const Tree, +const GraphemeUnit = struct { + slice: Grapheme, + tree: *const Tree, - fn startByte(unit: GraphemeUnit) u32 { - return unit.slice.offset; - } + fn startByte(unit: GraphemeUnit) u32 { + return unit.slice.offset; + } - fn endByte(unit: GraphemeUnit) u32 { - return unit.slice.offset + unit.slice.len; - } + fn endByte(unit: GraphemeUnit) u32 { + 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).?; + 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 = prev_node.endByte(), + .start = parent_node.startByte(), .end = parent_node.endByte(), .tree = unit.tree, }; - } - } - - fn at(unit: GraphemeUnit, graphemes: Graphemes, - text: []const u8, index: u32) GraphemeUnit { + const prev_node = parent_node.child(siblings - 1).?; return .{ - .slice = graphemeAt(graphemes, text, index), + .start = prev_node.endByte(), + .end = parent_node.endByte(), .tree = unit.tree, }; } + } - fn before(unit: GraphemeUnit, graphemes: Graphemes, - text: []const u8, index: u32) GraphemeUnit { - return .{ - .slice = graphemeBefore(graphemes, text, index), - .tree = unit.tree, - }; - } - }; + fn at(unit: GraphemeUnit, text: []const u8, index: u32) GraphemeUnit { + return .{ + .slice = graphemeAt(text, index), + .tree = unit.tree, + }; + } + + fn before(unit: GraphemeUnit, + text: []const u8, index: u32) GraphemeUnit { + return .{ + .slice = graphemeBefore(text, index), + .tree = unit.tree, + }; + } +}; + +// TODO: undo +pub const Unit = union(enum) { + node: Node, + span: Span, + grapheme: GraphemeUnit, fn startByte(unit: Unit) u32 { return switch (unit) { @@ -118,11 +141,11 @@ pub const Unit = union(enum) { }; } - pub fn up(unit: Unit) Unit { + 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 }) + .up(.{ .node = parent_node }, text) else .{ .node = parent_node } else unit, @@ -130,7 +153,7 @@ pub const Unit = union(enum) { const parent_node = span.parent(); return if (parent_node.startByte() == span.start and parent_node.endByte() == span.end) - .up(.{ .node = parent_node }) + .up(.{ .node = parent_node }, text) else .{ .node = parent_node }; }, @@ -138,14 +161,14 @@ pub const Unit = union(enum) { const parent_span = grapheme.parent(); return if (parent_span.start == grapheme.startByte() and parent_span.end == grapheme.endByte()) - .up(.{ .span = parent_span }) + .up(.{ .span = parent_span }, text) else .{ .span = parent_span }; }, } } - pub fn right(unit: Unit, graphemes: Graphemes, text: []const u8) Unit { + pub fn right(unit: Unit, text: []const u8) Unit { switch (unit) { .node => |node| if (node.nextSibling()) |sibling| { const prev = node.endByte(); @@ -163,21 +186,21 @@ pub const Unit = union(enum) { }; }, .span => |span| { - const parent_node = unit.up().node; + const parent_node = unit.up(text).node; if (parent_node.firstChildForByte(span.end)) |next_node| return .{ .node = next_node }; }, .grapheme => |grapheme| { const start = grapheme.endByte(); - const next_grapheme = grapheme.at(graphemes, text, start); + const next_grapheme = grapheme.at(text, start); if (eql(next_grapheme.parent(), grapheme.parent())) return .{ .grapheme = next_grapheme }; }, } - return .up(unit); + return .up(unit, text); } - pub fn left(unit: Unit, graphemes: Graphemes, text: []const u8) Unit { + pub fn left(unit: Unit, text: []const u8) Unit { switch (unit) { .node => |node| if (node.prevSibling()) |sibling| { const prev = sibling.endByte(); @@ -195,7 +218,7 @@ pub const Unit = union(enum) { }; }, .span => |span| { - const parent_node = span.parent(); + 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 }; @@ -206,15 +229,15 @@ pub const Unit = union(enum) { }, .grapheme => |grapheme| { const end = grapheme.slice.offset; - const prev_grapheme = grapheme.before(graphemes, text, end); + const prev_grapheme = grapheme.before(text, end); if (eql(prev_grapheme.parent(), grapheme.parent())) return .{ .grapheme = prev_grapheme }; }, } - return .up(unit); + return .up(unit, text); } - pub fn down(unit: Unit, graphemes: Graphemes, text: []const u8) Unit { + pub fn down(unit: Unit, text: []const u8) Unit { switch (unit) { .node => |node| { const start = node.startByte(); @@ -222,17 +245,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 }, graphemes, text) + .down(.{ .node = child }, text) else .{ .node = child } else .down(.{ .span = .{ .start = start, .end = end, .tree = node.tree } - }, graphemes, text); + }, text); }, - // TODO: history .span => |span| return .{ .grapheme = .{ - .slice = graphemeAt(graphemes, text, span.start), + // TODO: history + .slice = graphemeAt(text, span.start), .tree = span.tree, } }, @@ -250,35 +273,3 @@ pub const Unit = union(enum) { }; } }; - -pub const Segment = struct { - head: Unit, - tail: Unit, - - pub fn contains(segment: Segment, grapheme: Grapheme) bool { - const start = @min(segment.head.startByte(), segment.tail.startByte()); - const end = @min(segment.head.endByte(), segment.tail.endByte()); - return (start <= grapheme.offset - and grapheme.offset + grapheme.len <= end); - } - - pub fn goUp(segment: *Segment) void { - segment.head = segment.head.up(); - segment.tail = segment.tail.up(); - } - - pub fn goRight(segment: *Segment, graphemes: Graphemes, text: []const u8) void { - segment.head = segment.head.right(graphemes, text); - segment.tail = segment.tail.right(graphemes, text); - } - - pub fn goLeft(segment: *Segment, graphemes: Graphemes, text: []const u8) void { - segment.head = segment.head.left(graphemes, text); - segment.tail = segment.tail.left(graphemes, text); - } - - pub fn goDown(segment: *Segment, graphemes: Graphemes, text: []const u8) void { - segment.head = segment.head.down(graphemes, text); - segment.tail = segment.tail.down(graphemes, text); - } -}; diff --git a/src/Token.zig b/src/Token.zig deleted file mode 100644 index 49b15bb..0000000 --- a/src/Token.zig +++ /dev/null @@ -1,103 +0,0 @@ -// Tokenizer -// SPDX-FileCopyrightText: 2025 Nguyễn Gia Phong -// SPDX-License-Identifier: GPL-3.0-or-later - -const order = std.math.order; -const std = @import("std"); - -const Node = tree_sitter.Node; -const Parser = tree_sitter.Parser; -const Tree = tree_sitter.Tree; -const TreeCursor = tree_sitter.TreeCursor; -const tree_sitter = @import("tree-sitter"); - -const CreateLanguage = @import("languages").Create; - -const Token = @This(); -text: []const u8, -node: ?Node = null, - -const Iterator = struct { - text: []const u8, - parser: *Parser, - tree: *Tree, - cursor: TreeCursor, - next_node: ?Node = null, - pos: u32 = 0, - - /// Returns the next tree-sitter node. - fn nextNode(self: *Iterator) ?Node { - if (self.next_node) |node| { - self.next_node = null; - return node; - } - const node = self.cursor.node(); - return if (self.cursor.gotoFirstChild()) - node - else if (self.cursor.gotoNextSibling()) - node - else while (self.cursor.gotoParent()) { - if (self.cursor.gotoNextSibling()) - break node; - } else null; - } - - pub fn next(self: *Iterator) ?Token { - if (self.pos == self.text.len) - return null; - while (self.nextNode()) |node| - if (node.childCount() > 0) { - const start = node.startByte(); - const end = node.endByte(); - switch (order(self.pos, start)) { - .lt => { - defer self.pos = start; - self.next_node = node; - return .{ .text = self.text[self.pos..start] }; - }, - .eq => { - defer self.pos = end; - return .{ .text = self.text[start..end], .node = node }; - }, - .gt => unreachable, - } - }; - switch (order(self.pos, self.text.len)) { - .lt => { - defer self.pos = @intCast(self.text.len); - return .{ .text = self.text[self.pos..] }; - }, - .eq => return null, - .gt => unreachable, - } - } - - pub fn reset(self: *Iterator) void { - self.cursor.reset(self.tree.rootNode()); - self.next_node = null; - self.pos = 0; - } - - pub fn deinit(self: *Iterator) void { - self.cursor.destroy(); - self.tree.destroy(); - self.parser.getLanguage().?.destroy(); - self.parser.destroy(); - self.* = undefined; - } -}; - -/// Parse text in given language and return an iterator of tokens. -pub fn ize(text: []const u8, createLanguage: CreateLanguage) error { - IncompatibleVersion, -}!Iterator { - const parser = Parser.create(); - try parser.setLanguage(createLanguage()); - const tree = parser.parseString(text, null).?; - return .{ - .text = text, - .parser = parser, - .tree = tree, - .cursor = tree.walk(), - }; -} diff --git a/src/main.janet b/src/main.janet index 8ca1215..4034dc9 100644 --- a/src/main.janet +++ b/src/main.janet @@ -2,24 +2,73 @@ # SPDX-FileCopyrightText: 2025 Nguyễn Gia Phong # SPDX-License-Identifier: GPL-3.0-or-later +(defn kay/select/move + [buf direction] + (let [data (buf :data) + selection (buf :selection)] + (struct ;(kvs buf) + :selection (match selection + {:head head :tail tail} + (struct ;(kvs selection) + :head (direction head data) + :tail (direction tail data)))))) + +(defn kay/select/head + [buf direction] + (let [data (buf :data) + selection (buf :selection)] + (struct ;(kvs buf) + :selection (match selection + {:head head :tail tail} + (struct ;(kvs selection) + :head (direction head data) + :tail tail))))) + +(defn kay/select/flip + [buf] + (struct ;(kvs buf) + :selection (match (buf :selection) + {:head head :tail tail} {:head tail :tail head}))) + (defn handle - [event] + [event buf] (match event - [:key-press key] (cond - (:matches key (chr "c") {:ctrl true}) [:quit] - (:matches key (chr "h") {}) (do (:go-up env) []) - (:matches key (chr "j") {}) (do (:go-right env) []) - (:matches key (chr "k") {}) (do (:go-left env) []) - (:matches key (chr "l") {}) (do (:go-down env) []) - []))) + {:winsize ws} (do (:resize kay/env ws) + [[] buf]) + {:key-press key} + (cond + (: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) + [[] buf]) + (:matches key (chr "p") {}) [[] (struct ;(kvs buf) + :scroll-row (max 0 (dec (buf :scroll-row))))] + (:matches key (chr "n") {}) [[] (struct ;(kvs buf) + :scroll-row (inc (buf :scroll-row)))] + (:matches key (chr "h") {}) [[] (kay/select/move buf :up)] + (:matches key (chr "j") {}) [[] (kay/select/move buf :right)] + (:matches key (chr "k") {}) [[] (kay/select/move buf :left)] + (:matches key (chr "l") {}) [[] (kay/select/move buf :down)] + (:matches key (chr "h") {:shift true}) [[] (kay/select/head buf :up)] + (:matches key (chr "j") {:shift true}) [[] (kay/select/head buf :right)] + (:matches key (chr "k") {:shift true}) [[] (kay/select/head buf :left)] + (:matches key (chr "l") {:shift true}) [[] (kay/select/head buf :down)] + (:matches key (chr ";") {}) [[] (kay/select/flip buf)] + [[] buf]) + {:paste paste} [[] (:paste kay/env buf paste)])) (defn run - [events] + [events buf] (if-let [event (array/pop events)] (unless (= :quit event) - (let [next-events (handle event)] - (:render env) - (run (array/join events next-events)))) - (run @[(:next-event env)]))) + (let [[next-events next-buf] (handle event buf)] + (:render kay/env next-buf) + (run (array/join events next-events) + next-buf))) + (run @[(:next-event kay/env)] + buf))) -(run @[]) +(with [buf (kay/open (with [f (file/open (string kay/path))] + (buffer (:read f :all))))] + (run @[] buf)) diff --git a/src/main.zig b/src/main.zig index f34d878..d26cefa 100644 --- a/src/main.zig +++ b/src/main.zig @@ -14,6 +14,7 @@ const ns_per_s = std.time.ns_per_s; const smp_allocator = std.heap.smp_allocator; const std = @import("std"); +const Graphemes = vaxis.Graphemes; const Key = vaxis.Key; const Loop = vaxis.Loop; const Style = vaxis.Cell.Style; @@ -24,71 +25,65 @@ const Winsize = vaxis.Winsize; const gwidth = vaxis.gwidth.gwidth; const Vaxis = vaxis.Vaxis; const vaxis = @import("vaxis"); - -const Parser = tree_sitter.Parser; -const tree_sitter = @import("tree-sitter"); +pub const panic = vaxis.panic_handler; const zsanett = @import("zsanett"); pub fn zsanettIntern(T: type) bool { return switch (T) { - Environment, Key, Loop(Event) => true, + Buffer, Environment, Key, Loop(Event), + Selection, Selection.Unit => true, else => false, }; } +const Buffer = @import("Buffer.zig"); const Config = @import("Config.zig"); -const selection = @import("selection.zig"); -const languages = @import("languages"); +const Selection = @import("Selection.zig"); + +pub var graphemes: Graphemes = undefined; const Event = union(enum) { key_press: Key, winsize: Winsize, + paste: []const u8, }; const Environment = struct { allocator: Allocator, loop: *Loop(Event), - parser: *Parser, - segment: *selection.Segment, - text: []const u8, tty: *Tty, vaxis: *Vaxis, - pub fn nextEvent(self: Environment) !Event { - while (true) { - const event = self.loop.nextEvent(); - switch (event) { - .key_press => return event, - .winsize => |ws| { - try self.vaxis.resize(self.allocator, - self.tty.anyWriter(), ws); - try self.render(); - }, - } - } + pub fn nextEvent(self: Environment) Event { + return self.loop.nextEvent(); } - pub fn render(self: Environment) !void { + pub fn render(self: Environment, buffer: Buffer) !void { const window = self.vaxis.window(); window.clear(); var col: u16 = 0; var row: u16 = 0; - var graphemes = self.vaxis.unicode.graphemeIterator(self.text); - while (graphemes.next()) |grapheme| { + var graphemes_iter = buffer.iterate(); + while (graphemes_iter.next()) |grapheme| { + const in_head = buffer.selection.inHead(grapheme); + const in_body = buffer.selection.inBody(grapheme); const style = Style { - .reverse = self.segment.contains(grapheme), + .reverse = in_head, + .ul_style = if (in_body) .single else .off, }; - const bytes = grapheme.bytes(self.text); + const bytes = buffer.bytes(grapheme); if (eql(u8, bytes, "\n")) { - const width = window.gwidth("$"); - defer col = 0; - defer row += 1; - window.writeCell(col, row, .{ - .char = .{ .grapheme = "$", .width = @intCast(width) }, - .style = style, - }); + while (col < window.width) : (col += 1) + window.writeCell(col, row, .{ + .char = .{ .grapheme = " " }, + .style = style, + }); + col = 0; + row += 1; + if (row >= window.height) + break; } else if (eql(u8, bytes, "\t")) { - var tab = self.vaxis.unicode.graphemeIterator(" "); + var tab = graphemes.iterator(" "); while (tab.next()) |g| { const b = g.bytes(" "); const width = window.gwidth(b); @@ -110,20 +105,23 @@ const Environment = struct { try self.vaxis.render(self.tty.anyWriter()); } - pub fn goUp(self: Environment) void { - self.segment.goUp(); + pub fn resize(env: Environment, ws: Winsize) !void { + try env.vaxis.resize(env.allocator, env.tty.anyWriter(), ws); } - pub fn goRight(self: Environment) void { - self.segment.goRight(self.vaxis.unicode.width_data.graphemes, self.text); + pub fn yank(env: Environment, buffer: Buffer) !void { + try env.vaxis.copyToSystemClipboard(env.tty.anyWriter(), + buffer.selectedBytes(), + env.allocator); } - pub fn goLeft(self: Environment) void { - self.segment.goLeft(self.vaxis.unicode.width_data.graphemes, self.text); + pub fn requestPaste(env: Environment) !void { + try env.vaxis.requestSystemClipboard(env.tty.anyWriter()); } - pub fn goDown(self: Environment) void { - self.segment.goDown(self.vaxis.unicode.width_data.graphemes, self.text); + pub fn paste(env: Environment, buffer: Buffer, data: []const u8) Buffer { + defer env.vaxis.opts.system_clipboard_allocator.?.free(data); + return buffer.paste(data); } }; @@ -137,45 +135,38 @@ pub fn main() !void { var tty = try Tty.init(); defer tty.deinit(); - var vx = try vaxis.init(allocator, .{}); + var vx = try vaxis.init(allocator, .{ + .system_clipboard_allocator = allocator, + }); defer vx.deinit(allocator, tty.anyWriter()); + graphemes = vx.unicode.width_data.graphemes; + defer graphemes = undefined; var loop = Loop(Event){ .tty = &tty, .vaxis = &vx }; try loop.init(); try loop.start(); defer loop.stop(); - const parser = Parser.create(); - defer parser.destroy(); - const args = try argsAlloc(allocator); defer argsFree(allocator, args); - const text = try cwd().readFileAlloc(allocator, args[1], maxInt(u32)); - defer allocator.free(text); try vx.enterAltScreen(tty.anyWriter()); try vx.queryTerminal(tty.anyWriter(), 1 * ns_per_s); // for alt screen - const language = languages.c(); - defer language.destroy(); - try parser.setLanguage(language); - const tree = parser.parseString(text, null).?; - defer tree.destroy(); - zsanett.init(); defer zsanett.deinit(); - var segment = selection.Segment{ - .head = .{ .node = tree.rootNode() }, - .tail = .{ .node = tree.rootNode() }, - }; - try zsanett.def("env", Environment{ + try zsanett.def("kay/path", args[1], "Path to file."); + try zsanett.def("kay/env", Environment{ .allocator = allocator, .loop = &loop, - .parser = parser, - .segment = &segment, - .text = text, .tty = &tty, .vaxis = &vx, - }, "eval environment"); + }, "Eval environment."); + try zsanett.defn(Buffer.open, .{ + .prefix = "kay", + .name = "open", + .documentation = "(kay/open text)\n\nOpen text for editing.", + .source = @src(), + }); switch (try zsanett.eval(void, @embedFile("main.janet"), "main.janet")) { .ret => {}, .err => |err| @panic(err), |