diff options
Diffstat (limited to 'src/Buffer.zig')
-rw-r--r-- | src/Buffer.zig | 202 |
1 files changed, 202 insertions, 0 deletions
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; +} |