~jamii/focus

ref: 6ca339cc4a715b8a5bac73b4345a1f5c07c6ddfc focus/lib/focus/line_wrapped_buffer.zig -rw-r--r-- 4.9 KiB
6ca339ccJamie Brandon Bugfixes 1 year, 5 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
const focus = @import("../focus.zig");
usingnamespace focus.common;
const App = focus.App;
const Buffer = focus.Buffer;
const meta = focus.meta;

pub const LineWrappedBuffer = struct {
    app: *App,
    buffer: *Buffer,
    max_chars_per_line: usize,
    wrapped_line_ranges: ArrayList([2]usize),

    pub fn init(app: *App, buffer: *Buffer, max_chars_per_line: usize) LineWrappedBuffer {
        var self = LineWrappedBuffer{
            .app = app,
            .buffer = buffer,
            .max_chars_per_line = max_chars_per_line,
            .wrapped_line_ranges = ArrayList([2]usize).init(app.allocator),
        };
        self.update();
        return self;
    }

    pub fn deinit(self: *LineWrappedBuffer) void {
        self.wrapped_line_ranges.deinit();
    }

    pub fn update(self: *LineWrappedBuffer) void {
        const wrapped_line_ranges = &self.wrapped_line_ranges;
        wrapped_line_ranges.resize(0) catch oom();
        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;
                            }
                            const char = maybe_line_end.getNextItem();
                            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.seekNextItem();
                            if (char == ' ') {
                                // commit to including this char
                                line_end = maybe_line_end.pos;
                            }
                            // 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;
                }
            }

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

    pub fn getLineColForPos(self: *LineWrappedBuffer, pos: usize) [2]usize {
        // TODO avoid hacky fake key
        var line = std.sort.binarySearch([2]usize, [2]usize{ pos, pos }, self.wrapped_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).?;
        // check next line to resolve ambiguity around putting the cursor before/after line wraps
        if (line + 1 < self.wrapped_line_ranges.items.len and pos == self.wrapped_line_ranges.items[line + 1][0]) line = line + 1;
        const line_range = self.wrapped_line_ranges.items[line];
        return .{ line, pos - line_range[0] };
    }

    pub fn getRangeForLine(self: *LineWrappedBuffer, line: usize) [2]usize {
        return self.wrapped_line_ranges.items[line];
    }

    pub fn getPosForLine(self: *LineWrappedBuffer, line: usize) usize {
        return self.getRangeForLine(line)[0];
    }

    pub fn getPosForLineCol(self: *LineWrappedBuffer, line: usize, col: usize) usize {
        const range = self.wrapped_line_ranges.items[line];
        return range[0] + min(col, range[1] - range[0]);
    }

    pub fn getLineStart(self: *LineWrappedBuffer, pos: usize) usize {
        const line_col = self.getLineColForPos(pos);
        return self.getRangeForLine(line_col[0])[0];
    }

    pub fn getLineEnd(self: *LineWrappedBuffer, pos: usize) usize {
        const line_col = self.getLineColForPos(pos);
        return self.getRangeForLine(line_col[0])[1];
    }

    pub fn countLines(self: *LineWrappedBuffer) usize {
        return self.wrapped_line_ranges.items.len;
    }
};