~jamii/focus

3fa8c8ac67c7caab97eef14cb9a112d41c92567b — Jamie Brandon 11 months ago 117735a
Remove Buffer.line_ranges
3 files changed, 221 insertions(+), 153 deletions(-)

M lib/focus/buffer.zig
M lib/focus/line_wrapped_buffer.zig
M lib/focus/tree.zig
M lib/focus/buffer.zig => lib/focus/buffer.zig +92 -117
@@ 65,7 65,6 @@ pub const Buffer = struct {
    doing: ArrayList(Edit),
    redos: ArrayList([]Edit),
    modified_since_last_save: bool,
    line_ranges: ArrayList([2]usize),
    completions: ArrayList([]const u8),
    // editors must unregister before buffer deinits
    editors: ArrayList(*Editor),


@@ 81,12 80,10 @@ pub const Buffer = struct {
            .doing = ArrayList(Edit).init(app.allocator),
            .redos = ArrayList([]Edit).init(app.allocator),
            .modified_since_last_save = false,
            .line_ranges = ArrayList([2]usize).init(app.allocator),
            .completions = ArrayList([]const u8).init(app.allocator),
            .editors = ArrayList(*Editor).init(app.allocator),
            .role = role,
        };
        self.updateLineRanges();
        return self;
    }



@@ 114,8 111,6 @@ pub const Buffer = struct {
        for (self.completions.items) |completion| self.app.allocator.free(completion);
        self.completions.deinit();

        self.line_ranges.deinit();

        for (self.undos.items) |edits| {
            for (edits) |edit| edit.deinit(self.app.allocator);
            self.app.allocator.free(edits);


@@ 243,27 238,22 @@ pub const Buffer = struct {
    }

    pub fn getPosForLine(self: *Buffer, line: usize) usize {
        return self.line_ranges.items[line][0];
        return self.tree.getPointForLineStart(line).?.pos;
    }

    // TODO should handle line out of range too?
    /// Panics on line out of range. Handles col out of range by truncating to end of line.
    pub fn getPosForLineCol(self: *Buffer, line: usize, col: usize) usize {
        const line_range = self.line_ranges.items[line];
        return line_range[0] + min(col, line_range[1] - line_range[0]);
        const start_point = self.tree.getPointForLineStart(line).?;
        var end_point = start_point;
        _ = end_point.searchForwards("\n");
        return start_point.pos + min(col, end_point.pos - start_point.pos);
    }

    pub fn getLineColForPos(self: *Buffer, pos: usize) [2]usize {
        // TODO avoid hacky fake key
        const line = std.sort.binarySearch([2]usize, [2]usize{ pos, pos }, self.line_ranges.items, {}, struct {
            fn compare(_: void, key: [2]usize, item: [2]usize) std.math.Order {
                if (key[0] < item[0]) return .lt;
                if (key[0] > item[1]) return .gt;
                return .eq;
            }
        }.compare).?;
        const line_range = self.line_ranges.items[line];
        return .{ line, pos - line_range[0] };
        var point = self.tree.getPointForPos(pos).?;
        if (point.searchBackwards("\n") == .Found) _ = point.seekNextByte();
        return .{ point.getLine(), pos - point.pos };
    }

    pub fn searchForwards(self: *Buffer, pos: usize, needle: []const u8) ?usize {


@@ 321,13 311,15 @@ pub const Buffer = struct {
    }

    pub fn getLineStart(self: *Buffer, pos: usize) usize {
        const line = self.getLineColForPos(pos)[0];
        return self.line_ranges.items[line][0];
        var point = self.tree.getPointForPos(pos).?;
        if (point.searchBackwards("\n") == .Found) _ = point.seekNextByte();
        return point.pos;
    }

    pub fn getLineEnd(self: *Buffer, pos: usize) usize {
        const line = self.getLineColForPos(pos)[0];
        return self.line_ranges.items[line][1];
        var point = self.tree.getPointForPos(pos).?;
        _ = point.searchForwards("\n");
        return point.pos;
    }

    pub fn copy(self: *Buffer, allocator: *Allocator, start: usize, end: usize) []const u8 {


@@ 343,7 335,6 @@ pub const Buffer = struct {

        self.addRangeToCompletions(line_start, line_end + bytes.len);

        self.updateLineRanges();
        self.modified_since_last_save = true;
        for (self.editors.items) |editor| {
            editor.updateAfterInsert(pos, bytes);


@@ 362,7 353,6 @@ pub const Buffer = struct {

        self.addRangeToCompletions(line_start, line_end - (end - start));

        self.updateLineRanges();
        self.modified_since_last_save = true;
        for (self.editors.items) |editor| {
            editor.updateAfterDelete(start, end);


@@ 380,7 370,6 @@ pub const Buffer = struct {
        self.tree = Tree.init(self.app.allocator);
        self.tree.insert(0, new_bytes);

        self.updateLineRanges();
        self.modified_since_last_save = true;
        for (self.editors.items) |editor| {
            editor.updateAfterReplace(line_colss.pop());


@@ 497,7 486,7 @@ pub const Buffer = struct {
    }

    pub fn countLines(self: *Buffer) usize {
        return self.line_ranges.items.len;
        return self.tree.getTotalNewlines() + 1;
    }

    pub fn getFilename(self: *Buffer) ?[]const u8 {


@@ 511,104 500,90 @@ pub const Buffer = struct {
        return (byte >= 'a' and byte <= 'z') or (byte >= 'A' and byte <= 'Z') or (byte >= '0' and byte <= '9') or (byte == '_');
    }

    fn updateLineRanges(self: *Buffer) void {
        const line_ranges = &self.line_ranges;
        line_ranges.resize(0) catch oom();

        var point = self.tree.getPointForPos(0).?;
        while (true) {
            const start = point.pos;
            while (!point.isAtEnd() and point.getNextByte() != '\n') : (_ = point.seekNextByte()) {}
            line_ranges.append(.{ start, point.pos }) catch oom();
            if (point.isAtEnd()) break;
            _ = point.seekNextByte();
        }
    }

    fn updateCompletions(self: *Buffer) void {
        if (self.role == .Preview) return;

        const completions = &self.completions;
        for (completions.items) |completion| self.app.allocator.free(completion);
        completions.resize(0) catch oom();

        var point = self.tree.getPointForPos(0).?;
        while (!point.isAtEnd()) {
            const start = point.pos;
            while (!point.isAtEnd() and isLikeIdent(point.getNextByte())) : (_ = point.seekNextByte()) {}
            if (point.pos > start)
                completions.append(self.tree.copy(self.app.allocator, start, point.pos)) catch oom();
            while (!point.isAtEnd() and !isLikeIdent(point.getNextByte())) : (_ = point.seekNextByte()) {}
        }

        std.sort.sort([]const u8, completions.items, {}, struct {
            fn lessThan(_: void, a: []const u8, b: []const u8) bool {
                return std.mem.lessThan(u8, a, b);
            }
        }.lessThan);
        //if (self.role == .Preview) return;
        //
        //const completions = &self.completions;
        //for (completions.items) |completion| self.app.allocator.free(completion);
        //completions.resize(0) catch oom();
        //
        //var point = self.tree.getPointForPos(0).?;
        //while (!point.isAtEnd()) {
        //const start = point.pos;
        //while (!point.isAtEnd() and isLikeIdent(point.getNextByte())) : (_ = point.seekNextByte()) {}
        //if (point.pos > start)
        //completions.append(self.tree.copy(self.app.allocator, start, point.pos)) catch oom();
        //while (!point.isAtEnd() and !isLikeIdent(point.getNextByte())) : (_ = point.seekNextByte()) {}
        //}
        //
        //std.sort.sort([]const u8, completions.items, {}, struct {
        //fn lessThan(_: void, a: []const u8, b: []const u8) bool {
        //return std.mem.lessThan(u8, a, b);
        //}
        //}.lessThan);
    }

    fn removeRangeFromCompletions(self: *Buffer, range_start: usize, range_end: usize) void {
        const completions = &self.completions;
        var point = self.tree.getPointForPos(range_start).?;
        while (point.pos < range_end) {
            const start = point.pos;
            while (point.pos < range_end and isLikeIdent(point.getNextByte())) : (_ = point.seekNextByte()) {}
            if (point.pos > start) {
                const completions_items = completions.items;
                const completion = self.tree.copy(self.app.frame_allocator, start, point.pos);
                var left: usize = 0;
                var right: usize = completions_items.len;

                const pos = pos: {
                    while (left < right) {
                        const mid = left + (right - left) / 2;
                        switch (std.mem.order(u8, completion, completions_items[mid])) {
                            .eq => break :pos mid,
                            .gt => left = mid + 1,
                            .lt => right = mid,
                        }
                    }
                    // completion should definitely exist in the list
                    @panic("Tried to remove non-existent completion");
                };

                const removed = completions.orderedRemove(pos);
                self.app.allocator.free(removed);
            }
            while (point.pos < range_end and !isLikeIdent(point.getNextByte())) : (_ = point.seekNextByte()) {}
        }
        //const completions = &self.completions;
        //var point = self.tree.getPointForPos(range_start).?;
        //while (point.pos < range_end) {
        //const start = point.pos;
        //while (point.pos < range_end and isLikeIdent(point.getNextByte())) : (_ = point.seekNextByte()) {}
        //if (point.pos > start) {
        //const completions_items = completions.items;
        //const completion = self.tree.copy(self.app.frame_allocator, start, point.pos);
        //var left: usize = 0;
        //var right: usize = completions_items.len;
        //
        //const pos = pos: {
        //while (left < right) {
        //const mid = left + (right - left) / 2;
        //switch (std.mem.order(u8, completion, completions_items[mid])) {
        //.eq => break :pos mid,
        //.gt => left = mid + 1,
        //.lt => right = mid,
        //}
        //}
        //// completion should definitely exist in the list
        //@panic("Tried to remove non-existent completion");
        //};
        //
        //const removed = completions.orderedRemove(pos);
        //self.app.allocator.free(removed);
        //}
        //while (point.pos < range_end and !isLikeIdent(point.getNextByte())) : (_ = point.seekNextByte()) {}
        //}
    }

    fn addRangeToCompletions(self: *Buffer, range_start: usize, range_end: usize) void {
        const completions = &self.completions;
        var point = self.tree.getPointForPos(range_start).?;
        while (point.pos < range_end) {
            const start = point.pos;
            while (point.pos < range_end and isLikeIdent(point.getNextByte())) : (_ = point.seekNextByte()) {}
            if (point.pos > start) {
                const completions_items = completions.items;
                const completion = self.tree.copy(self.app.allocator, start, point.pos);
                var left: usize = 0;
                var right: usize = completions_items.len;

                const pos = pos: {
                    while (left < right) {
                        const mid = left + (right - left) / 2;
                        switch (std.mem.order(u8, completion, completions_items[mid])) {
                            .eq => break :pos mid,
                            .gt => left = mid + 1,
                            .lt => right = mid,
                        }
                    }
                    // completion might not be in the list, but this is where it should be added
                    break :pos left;
                };

                completions.insert(pos, completion) catch oom();
            }
            while (point.pos < range_end and !isLikeIdent(point.getNextByte())) : (_ = point.seekNextByte()) {}
        }
        //const completions = &self.completions;
        //var point = self.tree.getPointForPos(range_start).?;
        //while (point.pos < range_end) {
        //const start = point.pos;
        //while (point.pos < range_end and isLikeIdent(point.getNextByte())) : (_ = point.seekNextByte()) {}
        //if (point.pos > start) {
        //const completions_items = completions.items;
        //const completion = self.tree.copy(self.app.allocator, start, point.pos);
        //var left: usize = 0;
        //var right: usize = completions_items.len;
        //
        //const pos = pos: {
        //while (left < right) {
        //const mid = left + (right - left) / 2;
        //switch (std.mem.order(u8, completion, completions_items[mid])) {
        //.eq => break :pos mid,
        //.gt => left = mid + 1,
        //.lt => right = mid,
        //}
        //}
        //// completion might not be in the list, but this is where it should be added
        //break :pos left;
        //};
        //
        //completions.insert(pos, completion) catch oom();
        //}
        //while (point.pos < range_end and !isLikeIdent(point.getNextByte())) : (_ = point.seekNextByte()) {}
        //}
    }

    pub fn getCompletionsInto(self: *Buffer, prefix: []const u8, results: *ArrayList([]const u8)) void {

M lib/focus/line_wrapped_buffer.zig => lib/focus/line_wrapped_buffer.zig +42 -35
@@ 28,47 28,54 @@ pub const LineWrappedBuffer = struct {
    pub fn update(self: *LineWrappedBuffer) void {
        const wrapped_line_ranges = &self.wrapped_line_ranges;
        wrapped_line_ranges.resize(0) catch oom();
        for (self.buffer.line_ranges.items) |real_line_range, real_line| {
            if (real_line_range[1] - real_line_range[0] <= self.max_chars_per_line) {
                wrapped_line_ranges.append(real_line_range) catch oom();
                continue;
            }
            const real_line_end = real_line_range[1];
            var line_start = real_line_range[0];
            while (true) {
                var line_end = line_start;
                var maybe_line_end = self.buffer.tree.getPointForPos(line_end).?;
                {
                    while (true) {
                        if (maybe_line_end.pos >= real_line_end) {
                            line_end = maybe_line_end.pos;
                            break;
                        }
                        const char = maybe_line_end.getNextByte();
                        if (maybe_line_end.pos - line_start > self.max_chars_per_line) {
                            // if we haven't soft wrapped yet, hard wrap before this char, otherwise use soft wrap
                            if (line_end == line_start) {
        var point = self.buffer.tree.getPointForPos(0).?;
        while (true) {
            const real_line_start = point.pos;
            _ = point.searchForwards("\n");
            const real_line_end = point.pos;

            if (real_line_end - real_line_start <= self.max_chars_per_line) {
                wrapped_line_ranges.append(.{ real_line_start, real_line_end }) catch oom();
            } else {
                var line_start = real_line_start;
                while (true) {
                    var line_end = line_start;
                    var maybe_line_end = self.buffer.tree.getPointForPos(line_end).?;
                    {
                        while (true) {
                            if (maybe_line_end.pos >= real_line_end) {
                                line_end = maybe_line_end.pos;
                                break;
                            }
                            break;
                        }
                        if (char == '\n') {
                            // wrap here
                            line_end = maybe_line_end.pos;
                            break;
                        }
                        _ = maybe_line_end.seekNextByte();
                        if (char == ' ') {
                            // commit to including this char
                            line_end = maybe_line_end.pos;
                            const char = maybe_line_end.getNextByte();
                            if (maybe_line_end.pos - line_start > self.max_chars_per_line) {
                                // if we haven't soft wrapped yet, hard wrap before this char, otherwise use soft wrap
                                if (line_end == line_start) {
                                    line_end = maybe_line_end.pos;
                                }
                                break;
                            }
                            if (char == '\n') {
                                // wrap here
                                line_end = maybe_line_end.pos;
                                break;
                            }
                            _ = maybe_line_end.seekNextByte();
                            if (char == ' ') {
                                // commit to including this char
                                line_end = maybe_line_end.pos;
                            }
                            // otherwise keep looking ahead
                        }
                        // otherwise keep looking ahead
                    }
                    self.wrapped_line_ranges.append(.{ line_start, line_end }) catch oom();
                    if (line_end >= real_line_end) break;
                    line_start = line_end;
                }
                self.wrapped_line_ranges.append(.{ line_start, line_end }) catch oom();
                if (line_end >= real_line_end) break;
                line_start = line_end;
            }

            if (point.isAtEnd()) break;
            _ = point.seekNextByte();
        }
    }


M lib/focus/tree.zig => lib/focus/tree.zig +87 -1
@@ 404,6 404,7 @@ pub const Point = struct {
    }

    pub fn searchForwards(self: *Point, needle: []const u8) Seek {
        assert(needle.len > 0);
        if (self.isAtEnd()) return .NotFound;
        const needle_start_char = needle[0];
        while (true) {


@@ 423,6 424,46 @@ pub const Point = struct {
            if (self.seekNextByte() == .NotFound) return .NotFound;
        }
    }

    pub fn searchBackwards(self: *Point, needle: []const u8) Seek {
        assert(needle.len > 0);
        const needle_start_char = needle[0];
        while (true) {
            if (self.seekPrevByte() == .NotFound) return .NotFound;
            const haystack_start_char = self.getNextByte();
            if (haystack_start_char == needle_start_char) {
                var end_point = self.*;
                var is_match = true;
                for (needle[1..]) |needle_char| {
                    if (end_point.seekNextByte() == .Found)
                        if (end_point.getNextByte() == needle_char)
                            continue;
                    is_match = false;
                    break;
                }
                if (is_match) return .Found;
            }
        }
    }

    pub fn getLine(self: Point) usize {
        var line: usize = 0;
        for (self.leaf.bytes[0..self.offset]) |char|
            line += @boolToInt(char == '\n');
        var branch = self.leaf.node.getParent().?;
        var child_ix = branch.findChild(self.leaf.node);
        while (true) {
            for (branch.num_newlines[0..child_ix]) |n|
                line += n;
            if (branch.node.getParent()) |parent| {
                child_ix = parent.findChild(branch.node);
                branch = parent;
            } else {
                break;
            }
        }
        return line;
    }
};

pub const Tree = struct {


@@ 700,7 741,6 @@ pub const Tree = struct {
    }

    pub fn searchForwards(self: Tree, start: usize, needle: []const u8) ?usize {
        assert(needle.len > 0);
        var point = self.getPointForPos(start).?;
        switch (point.searchForwards(needle)) {
            .Found => return point.pos,


@@ 708,10 748,22 @@ pub const Tree = struct {
        }
    }

    pub fn searchBackwards(self: Tree, start: usize, needle: []const u8) ?usize {
        var point = self.getPointForPos(start).?;
        switch (point.searchBackwards(needle)) {
            .Found => return point.pos,
            .NotFound => return null,
        }
    }

    pub fn getTotalBytes(self: Tree) usize {
        return self.root.sumNumBytes();
    }

    pub fn getTotalNewlines(self: Tree) usize {
        return self.root.sumNumNewlines();
    }

    fn getDepth(self: Tree) usize {
        var depth: usize = 0;
        var node = self.root.node;


@@ 920,6 972,39 @@ test "search forwards" {
    assert(meta.deepEqual(expected.items, actual.items));
}

test "search backwards" {
    const cm: []const u8 = (std.fs.cwd().openFile("/home/jamie/huge.js", .{}) catch unreachable).readToEndAlloc(std.testing.allocator, std.math.maxInt(usize)) catch unreachable;
    defer std.testing.allocator.free(cm);

    var tree = Tree.init(std.testing.allocator);
    defer tree.deinit();
    tree.insert(0, cm);

    const needle = "className";

    var expected = ArrayList(usize).init(std.testing.allocator);
    defer expected.deinit();
    {
        var start: usize = 0;
        while (std.mem.indexOfPos(u8, cm, start, needle)) |pos| {
            expected.append(pos) catch oom();
            start = pos + 1;
        }
    }

    var actual = ArrayList(usize).init(std.testing.allocator);
    defer actual.deinit();
    {
        var start: usize = 0;
        while (tree.searchForwards(start, needle)) |pos| {
            actual.append(pos) catch oom();
            start = pos + 1;
        }
    }

    assert(meta.deepEqual(expected.items, actual.items));
}

test "get line start" {
    const cm: []const u8 = (std.fs.cwd().openFile("/home/jamie/huge.js", .{}) catch unreachable).readToEndAlloc(std.testing.allocator, std.math.maxInt(usize)) catch unreachable;
    defer std.testing.allocator.free(cm);


@@ 942,6 1027,7 @@ test "get line start" {
    for (expected.items) |pos, line| {
        const point = tree.getPointForLineStart(line).?;
        expectEqual(pos, point.pos);
        expectEqual(line, point.getLine());
    }

    expectEqual(tree.getPointForLineStart(expected.items.len), null);