summaryrefslogtreecommitdiff
path: root/src/buffer.hh
blob: 71c5cd510c8885618d2c5c5bbe29f71144762479 (plain)
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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
#ifndef buffer_hh_INCLUDED
#define buffer_hh_INCLUDED

#include "clock.hh"
#include "coord.hh"
#include "constexpr_utils.hh"
#include "enum.hh"
#include "file.hh"
#include "optional.hh"
#include "range.hh"
#include "safe_ptr.hh"
#include "scope.hh"
#include "shared_string.hh"
#include "value.hh"
#include "vector.hh"

#include <sys/types.h>
#include <ctime>

namespace Kakoune
{

enum class EolFormat
{
    Lf,
    Crlf
};

constexpr auto enum_desc(Meta::Type<EolFormat>)
{
    return make_array<EnumDesc<EolFormat>>({
        { EolFormat::Lf, "lf" },
        { EolFormat::Crlf, "crlf" },
    });
}

enum class ByteOrderMark
{
    None,
    Utf8
};

constexpr auto enum_desc(Meta::Type<ByteOrderMark>)
{
    return make_array<EnumDesc<ByteOrderMark>>({
        { ByteOrderMark::None, "none" },
        { ByteOrderMark::Utf8, "utf8" },
    });
}

class Buffer;

constexpr timespec InvalidTime = { -1, -1 };

// A BufferIterator permits to iterate over the characters of a buffer
class BufferIterator
{
public:
    using value_type = char;
    using difference_type = ssize_t;
    using pointer = const value_type*;
    using reference = const value_type&;
    // computing the distance between two iterator can be
    // costly, so this is not strictly random access
    using iterator_category = std::bidirectional_iterator_tag;

    BufferIterator() noexcept : m_buffer{nullptr}, m_line{} {}
    BufferIterator(const Buffer& buffer, BufferCoord coord) noexcept;

    bool operator== (const BufferIterator& iterator) const noexcept;
    std::strong_ordering operator<=>(const BufferIterator& iterator) const noexcept;
    bool operator== (const BufferCoord& coord) const noexcept;

    const char& operator* () const noexcept;
    const char& operator[](size_t n) const noexcept;
    size_t operator- (const BufferIterator& iterator) const;

    explicit operator bool() const { return static_cast<bool>(m_buffer); }

    BufferIterator operator+ (ByteCount size) const;
    BufferIterator operator- (ByteCount size) const;

    BufferIterator& operator+= (ByteCount size);
    BufferIterator& operator-= (ByteCount size);

    BufferIterator& operator++ ();
    BufferIterator& operator-- ();

    BufferIterator operator++ (int);
    BufferIterator operator-- (int);

    const BufferCoord& coord() const noexcept { return m_coord; }
    explicit operator BufferCoord() const noexcept { return m_coord; }
    using Sentinel = BufferCoord;

private:
    SafePtr<const Buffer> m_buffer;
    BufferCoord m_coord;
    StringView m_line;
};

using BufferLines = Vector<StringDataPtr, MemoryDomain::BufferContent>;
using BufferRange = Range<BufferCoord>;

// A Buffer is a in-memory representation of a file
//
// The Buffer class permits to read and mutate this file
// representation. It also manage modifications undo/redo and
// provides tools to deal with the line/column nature of text.
class Buffer final : public SafeCountable, public Scope, private OptionManagerWatcher
{
public:
    enum class Flags
    {
        None     = 0,
        File     = 1 << 0,
        New      = 1 << 1,
        Fifo     = 1 << 2,
        NoUndo   = 1 << 3,
        NoHooks  = 1 << 4,
        Debug    = 1 << 5,
        ReadOnly = 1 << 6,
    };
    friend constexpr bool with_bit_ops(Meta::Type<Flags>) { return true; }

    enum class HistoryId : size_t { First = 0, Invalid = (size_t)-1 };

    Buffer(String name, Flags flags, BufferLines lines,
           ByteOrderMark bom = ByteOrderMark::None,
           EolFormat eolformat = EolFormat::Lf,
           FsStatus fs_status = {InvalidTime, {}, {}});
    Buffer(const Buffer&) = delete;
    Buffer& operator= (const Buffer&) = delete;
    ~Buffer();

    Flags flags() const { return m_flags; }
    Flags& flags() { return m_flags; }

    bool set_name(String name);
    void update_display_name();

    BufferRange insert(BufferCoord pos, StringView content);
    BufferCoord erase(BufferCoord begin, BufferCoord end);
    BufferRange replace(BufferCoord begin, BufferCoord end, StringView content);

    size_t          timestamp() const;
    void            set_fs_status(FsStatus);
    const FsStatus& fs_status() const;

    void           commit_undo_group();
    bool           undo(size_t count = 1);
    bool           redo(size_t count = 1);
    bool           move_to(HistoryId id);
    HistoryId      current_history_id() const noexcept { return m_history_id; }
    HistoryId      next_history_id() const noexcept { return (HistoryId)m_history.size(); }

    String         string(BufferCoord begin, BufferCoord end) const;
    StringView     substr(BufferCoord begin, BufferCoord end) const;

    const char&    byte_at(BufferCoord c) const;
    ByteCount      distance(BufferCoord begin, BufferCoord end) const;
    BufferCoord    advance(BufferCoord coord, ByteCount count) const;
    BufferCoord    next(BufferCoord coord) const;
    BufferCoord    prev(BufferCoord coord) const;

    BufferCoord    char_next(BufferCoord coord) const;
    BufferCoord    char_prev(BufferCoord coord) const;

    BufferCoord    back_coord() const;
    BufferCoord    end_coord() const;

    bool           is_valid(BufferCoord c) const;
    bool           is_end(BufferCoord c) const;

    BufferIterator begin() const;
    BufferIterator end() const;
    LineCount      line_count() const;

    Optional<BufferCoord> last_modification_coord() const;

    StringView operator[](LineCount line) const
    { return m_lines[line]; }

    const StringDataPtr& line_storage(LineCount line) const
    { return m_lines.get_storage(line); }

    // returns an iterator at given coordinates. clamp line_and_column
    BufferIterator iterator_at(BufferCoord coord) const;

    // returns nearest valid coordinates from given ones
    BufferCoord clamp(BufferCoord coord) const;

    BufferCoord offset_coord(BufferCoord coord, CharCount offset, ColumnCount) const;
    BufferCoordAndTarget offset_coord(BufferCoordAndTarget coord, LineCount offset, ColumnCount tabstop) const;

    const String& name() const { return m_name; }
    const String& display_name() const { return m_display_name; }

    // returns true if the buffer is in a different state than
    // the last time it was saved
    bool is_modified() const;

    // notify the buffer that it was saved in the current state
    void notify_saved(FsStatus status);

    ValueMap& values() const { return m_values; }

    void run_hook_in_own_context(Hook hook, StringView param,
                                 String client_name = {});

    void reload(BufferLines lines, ByteOrderMark bom, EolFormat eolformat, FsStatus status);

    void check_invariant() const;

    struct Change
    {
        enum Type : char { Insert, Erase };
        Type type;
        BufferCoord begin;
        BufferCoord end;
    };
    ConstArrayView<Change> changes_since(size_t timestamp) const;

    String debug_description() const;

    // Methods called by the buffer manager
    void on_registered();
    void on_unregistered();

    void throw_if_read_only() const;

    // A Modification holds a single atomic modification to Buffer
    struct Modification
    {
        enum Type { Insert, Erase };

        Type type;
        BufferCoord coord;
        StringDataPtr content;

        Modification inverse() const;
    };

    using UndoGroup = Vector<Modification, MemoryDomain::BufferMeta>;

    struct HistoryNode : UseMemoryDomain<MemoryDomain::BufferMeta>
    {
        HistoryNode(HistoryId parent);

        HistoryId parent;
        HistoryId redo_child = HistoryId::Invalid;
        TimePoint committed;
        UndoGroup undo_group;
    };

    const Vector<HistoryNode>& history() const { return m_history; }
    const UndoGroup& current_undo_group() const { return m_current_undo_group; }

private:
    void on_option_changed(const Option& option) override;

    BufferRange do_insert(BufferCoord pos, StringView content);
    BufferCoord do_erase(BufferCoord begin, BufferCoord end);

    void apply_modification(const Modification& modification);
    void revert_modification(const Modification& modification);

    struct LineList : BufferLines
    {
        [[gnu::always_inline]]
        StringDataPtr& get_storage(LineCount line)
        { return BufferLines::operator[]((int)line); }

        [[gnu::always_inline]]
        const StringDataPtr& get_storage(LineCount line) const
        { return BufferLines::operator[]((int)line); }

        [[gnu::always_inline]]
        StringView operator[](LineCount line) const
        { return get_storage(line)->strview(); }

        StringView front() const { return BufferLines::front()->strview(); }
        StringView back() const { return BufferLines::back()->strview(); }
    };
    LineList m_lines;

    String m_name;
    String m_display_name;
    Flags  m_flags;

    Vector<HistoryNode> m_history;
    HistoryId           m_history_id = HistoryId::Invalid;
    HistoryId           m_last_save_history_id = HistoryId::Invalid;
    UndoGroup           m_current_undo_group;

          HistoryNode& history_node(HistoryId id)       { return m_history[(size_t)id]; }
    const HistoryNode& history_node(HistoryId id) const { return m_history[(size_t)id]; }
          HistoryNode& current_history_node()           { return m_history[(size_t)m_history_id]; }
    const HistoryNode& current_history_node()     const { return m_history[(size_t)m_history_id]; }

    Vector<Change, MemoryDomain::BufferMeta> m_changes;

    FsStatus m_fs_status;

    // Values are just data holding by the buffer, they are not part of its
    // observable state
    mutable ValueMap m_values;
};

}

#include "buffer.inl.hh"

#endif // buffer_hh_INCLUDED