summaryrefslogtreecommitdiff
path: root/src/ring.hh
blob: 54223904c92538ba16f0130fa0b1fc854352c63b (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
/*
 * Copyright (C) 2002,2009,2010 Red Hat, Inc.
 *
 * This library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library.  If not, see <https://www.gnu.org/licenses/>.
 *
 * Red Hat Author(s): Behdad Esfahbod
 */

/* The interfaces in this file are subject to change at any time. */

#pragma once

#include <gio/gio.h>
#include <vte/vte.h>

#include "vterowdata.hh"
#include "vtestream.h"

#ifdef WITH_SIXEL
#include "cairo-glue.hh"
#include "image.hh"
#include <map>
#include <memory>
#endif

#include <type_traits>

typedef struct _VteVisualPosition {
	long row, col;
} VteVisualPosition;

namespace vte {

namespace base {

/*
 * Ring:
 *
 * A scrollback buffer ring.
 */
class Ring {
public:
        typedef guint32 hyperlink_idx_t;
        // FIXME make this size_t (or off_t?)
        typedef gulong row_t;
        typedef glong column_t;

        static const row_t kDefaultMaxRows = VTE_SCROLLBACK_INIT;

        Ring(row_t max_rows = kDefaultMaxRows,
             bool has_streams = false);
        ~Ring();

        // prevent accidents
        Ring(Ring& o) = delete;
        Ring(Ring const& o) = delete;
        Ring(Ring&& o) = delete;
        Ring& operator= (Ring& o) = delete;
        Ring& operator= (Ring const& o) = delete;
        Ring& operator= (Ring&& o) = delete;

        inline bool contains(row_t position) const {
                return (position >= m_start && position < m_end);
        }

        inline row_t delta() const { return m_start; }
        inline row_t length() const { return m_end - m_start; }
        inline row_t next() const { return m_end; }

        //FIXMEchpe rename this to at()
        //FIXMEchpe use references not pointers
        VteRowData const* index(row_t position); /* const? */
        VteRowData* index_writable(row_t position);
        bool is_soft_wrapped(row_t position);

        void hyperlink_maybe_gc(row_t increment);
        hyperlink_idx_t get_hyperlink_idx(char const* hyperlink);
        hyperlink_idx_t get_hyperlink_at_position(row_t position,
                                                  column_t col,
                                                  bool update_hover_idx,
                                                  char const** hyperlink);

        row_t reset();
        void resize(row_t max_rows = kDefaultMaxRows);
        void shrink(row_t max_len = kDefaultMaxRows);
        VteRowData* insert(row_t position, guint8 bidi_flags);
        VteRowData* append(guint8 bidi_flags);
        void remove(row_t position);
        void drop_scrollback(row_t position);
        void set_visible_rows(row_t rows);
        void rewrap(column_t columns,
                    VteVisualPosition** markers);
        bool write_contents(GOutputStream* stream,
                            VteWriteFlags flags,
                            GCancellable* cancellable,
                            GError** error);

private:

        #ifdef VTE_DEBUG
        void validate() const;
        #endif

        inline GString* hyperlink_get(hyperlink_idx_t idx) const { return (GString*)g_ptr_array_index(m_hyperlinks, idx); }

        inline VteRowData* get_writable_index(row_t position) const { return &m_array[position & m_mask]; }

        void hyperlink_gc();
        hyperlink_idx_t get_hyperlink_idx_no_update_current(char const* hyperlink);

        typedef struct _CellAttrChange {
                gsize text_end_offset;  /* offset of first character no longer using this attr */
                VteStreamCellAttr attr;
        } CellAttrChange;

        typedef struct _RowRecord {
                size_t text_start_offset;  /* offset where text of this row begins */
                size_t attr_start_offset;  /* offset of the first character's attributes */
                int soft_wrapped: 1;      /* end of line is not '\n' */
                int is_ascii: 1;          /* for rewrapping speedup: guarantees that line contains 32..126 bytes only. Can be 0 even when ascii only. */
                guint8 bidi_flags: 4;
        } RowRecord;

        static_assert(std::is_standard_layout_v<RowRecord> && std::is_trivial_v<RowRecord>, "Ring::RowRecord is not POD");

        /* Represents a cell position, see ../doc/rewrap.txt */
        typedef struct _CellTextOffset {
                size_t text_offset;    /* byte offset in text_stream (or perhaps beyond) */
                int fragment_cells;  /* extra number of cells to walk within a multicell character */
                int eol_cells;       /* -1 if over a character, >=0 if at EOL or beyond */
        } CellTextOffset;

        static_assert(std::is_standard_layout_v<CellTextOffset> && std::is_trivial_v<CellTextOffset>, "Ring::CellTextOffset is not POD");

        inline bool read_row_record(RowRecord* record /* out */,
                                    row_t position)
        {
                return _vte_stream_read(m_row_stream,
                                        position * sizeof(*record),
                                        (char*)record,
                                        sizeof(*record));
        }

        inline void append_row_record(RowRecord const* record,
                                      row_t position)
        {
                _vte_stream_append(m_row_stream,
                                   (char const*)record,
                                   sizeof(*record));
        }

        bool frozen_row_column_to_text_offset(row_t position,
                                              column_t column,
                                              CellTextOffset* offset);
        bool frozen_row_text_offset_to_column(row_t position,
                                              CellTextOffset const* offset,
                                              column_t* column);

        bool write_row(GOutputStream* stream,
                       VteRowData* row,
                       VteWriteFlags flags,
                       GCancellable* cancellable,
                       GError** error);

        void ensure_writable(row_t position);
        void ensure_writable_room();

        void freeze_one_row();
        void maybe_freeze_one_row();
        void thaw_one_row();
        void discard_one_row();
        void maybe_discard_one_row();

        void freeze_row(row_t position,
                        VteRowData const* row);
        void thaw_row(row_t position,
                      VteRowData* row,
                      bool do_truncate,
                      int hyperlink_column,
                      char const** hyperlink);
        void reset_streams(row_t position);

	row_t m_max;
	row_t m_start{0};
        row_t m_end{0};

	/* Writable */
	row_t m_writable{0};
        row_t m_mask{31};
	VteRowData *m_array;

        /* Storage:
         *
         * row_stream contains records of VteRowRecord for each physical row.
         * (This stream is regenerated when the contents rewrap on resize.)
         *
         * text_stream is the text in UTF-8.
         *
         * attr_stream contains entries that consist of:
         *  - a VteCellAttrChange.
         *  - a string of attr.hyperlink_length length containing the (typically empty) hyperlink data.
         *    As far as the ring is concerned, this hyperlink data is opaque. Only the caller cares that
         *    if nonempty, it actually contains the ID and URI separated with a semicolon. Not NUL terminated.
         *  - 2 bytes repeating attr.hyperlink_length so that we can walk backwards.
         */
	bool m_has_streams;
	VteStream *m_attr_stream, *m_text_stream, *m_row_stream;
	size_t m_last_attr_text_start_offset{0};
	VteCellAttr m_last_attr;
	GString *m_utf8_buffer;

	VteRowData m_cached_row;
	row_t m_cached_row_num{(row_t)-1};

        row_t m_visible_rows{0};  /* to keep at least a screenful of lines in memory, bug 646098 comment 12 */

        GPtrArray *m_hyperlinks;  /* The hyperlink pool. Contains GString* items.
                                   [0] points to an empty GString, [1] to [VTE_HYPERLINK_COUNT_MAX] contain the id;uri pairs. */
        char m_hyperlink_buf[VTE_HYPERLINK_TOTAL_LENGTH_MAX + 1];  /* One more hyperlink buffer to get the value if it's not placed in the pool. */
        hyperlink_idx_t m_hyperlink_highest_used_idx{0};  /* 0 if no hyperlinks at all in the pool. */
        hyperlink_idx_t m_hyperlink_current_idx{0};  /* The hyperlink idx used for newly created cells.
                                                   Must not be GC'd even if doesn't occur onscreen. */
        hyperlink_idx_t m_hyperlink_hover_idx{0};  /* The hyperlink idx of the hovered cell.
                                                 An idx is allocated on hover even if the cell is scrolled out to the streams. */
        row_t m_hyperlink_maybe_gc_counter{0};  /* Do a GC when it reaches 65536. */

#ifdef WITH_SIXEL

private:
        size_t m_next_image_priority{0};
        size_t m_image_fast_memory_used{0};

        /* m_image_priority_map stores the Image. key is the priority of the image. */
        using image_map_type = std::map<size_t, std::unique_ptr<vte::image::Image>>;
        image_map_type m_image_map{};

        /* m_image_by_top_map stores only an iterator to the Image in m_image_priority_map;
         * key is the top row of the image.
         */
        using image_by_top_map_type = std::multimap<row_t, vte::image::Image*>;
        image_by_top_map_type m_image_by_top_map{};

        void image_gc() noexcept;
        void image_gc_region() noexcept;
        void unlink_image_from_top_map(vte::image::Image const* image) noexcept;
        void rebuild_image_top_map() /* throws */;
        bool rewrap_images_in_range(image_by_top_map_type::iterator& it,
                                    size_t text_start_ofs,
                                    size_t text_end_ofs,
                                    row_t new_row_index) noexcept;

public:
        auto const& image_map() const noexcept { return m_image_map; }

        void append_image(vte::Freeable<cairo_surface_t> surface,
                          int pixelwidth,
                          int pixelheight,
                          long left,
                          long top,
                          long cell_width,
                          long cell_height) /* throws */;

#endif /* WITH_SIXEL */
};

}; /* namespace base */

}; /* namespace vte */

G_BEGIN_DECLS

/* temp compat API */

typedef vte::base::Ring VteRing;

static inline bool _vte_ring_contains(VteRing *ring, gulong position) { return ring->contains(position); }
static inline glong _vte_ring_delta(VteRing *ring) { return ring->delta(); }
static inline glong _vte_ring_length(VteRing *ring) { return ring->length(); }
static inline glong _vte_ring_next(VteRing *ring) { return ring->next(); }
static inline const VteRowData *_vte_ring_index (VteRing *ring, gulong position) { return ring->index(position); }
static inline VteRowData *_vte_ring_index_writable (VteRing *ring, gulong position) { return ring->index_writable(position); }
static inline void _vte_ring_hyperlink_maybe_gc (VteRing *ring, gulong increment) { ring->hyperlink_maybe_gc(increment); }
static inline auto _vte_ring_get_hyperlink_idx (VteRing *ring, const char *hyperlink) { return ring->get_hyperlink_idx(hyperlink); }
static inline auto _vte_ring_get_hyperlink_at_position (VteRing *ring, gulong position, int col, bool update_hover_idx, const char **hyperlink) { return ring->get_hyperlink_at_position(position, col, update_hover_idx, hyperlink); }
static inline long _vte_ring_reset (VteRing *ring) { return ring->reset(); }
static inline void _vte_ring_resize (VteRing *ring, gulong max_rows) { ring->resize(max_rows); }
static inline void _vte_ring_shrink (VteRing *ring, gulong max_len) { ring->shrink(max_len); }
static inline VteRowData *_vte_ring_insert (VteRing *ring, gulong position, guint8 bidi_flags) { return ring->insert(position, bidi_flags); }
static inline VteRowData *_vte_ring_append (VteRing *ring, guint8 bidi_flags) { return ring->append(bidi_flags); }
static inline void _vte_ring_remove (VteRing *ring, gulong position) { ring->remove(position); }
static inline void _vte_ring_drop_scrollback (VteRing *ring, gulong position) { ring->drop_scrollback(position); }
static inline void _vte_ring_set_visible_rows (VteRing *ring, gulong rows) { ring->set_visible_rows(rows); }
static inline void _vte_ring_rewrap (VteRing *ring, glong columns, VteVisualPosition **markers) { ring->rewrap(columns, markers); }
static inline gboolean _vte_ring_write_contents (VteRing *ring,
                                                 GOutputStream *stream,
                                                 VteWriteFlags flags,
                                                 GCancellable *cancellable,
                                                 GError **error) { return ring->write_contents(stream, flags, cancellable, error); }

G_END_DECLS