File: | _build/../src/ring.cc |
Warning: | line 685, column 13 Dereference of null pointer |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | /* | |||
2 | * Copyright (C) 2002,2009,2010 Red Hat, Inc. | |||
3 | * | |||
4 | * This library is free software; you can redistribute it and/or | |||
5 | * modify it under the terms of the GNU Lesser General Public | |||
6 | * License as published by the Free Software Foundation; either | |||
7 | * version 2.1 of the License, or (at your option) any later version. | |||
8 | * | |||
9 | * This library is distributed in the hope that it will be useful, | |||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
12 | * Lesser General Public License for more details. | |||
13 | * | |||
14 | * You should have received a copy of the GNU Lesser General Public | |||
15 | * License along with this library; if not, write to the Free Software | |||
16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |||
17 | * | |||
18 | * Red Hat Author(s): Nalin Dahyabhai, Behdad Esfahbod | |||
19 | */ | |||
20 | ||||
21 | #include "config.h" | |||
22 | ||||
23 | #include "debug.h" | |||
24 | #include "ring.hh" | |||
25 | #include "bterowdata.hh" | |||
26 | ||||
27 | #include <string.h> | |||
28 | ||||
29 | /* | |||
30 | * Copy the common attributes from BteCellAttr to BteStreamCellAttr or vice versa. | |||
31 | */ | |||
32 | static inline void | |||
33 | _attrcpy (void *dst, void *src) | |||
34 | { | |||
35 | memcpy(dst, src, BTE_CELL_ATTR_COMMON_BYTES12); | |||
36 | } | |||
37 | ||||
38 | using namespace bte::base; | |||
39 | ||||
40 | /* | |||
41 | * BteRing: A buffer ring | |||
42 | */ | |||
43 | ||||
44 | #ifdef BTE_DEBUG | |||
45 | void | |||
46 | Ring::validate()do { } while(0) const | |||
47 | { | |||
48 | _bte_debug_print(BTE_DEBUG_RING,do { } while(0) | |||
49 | " Delta = %lu, Length = %lu, Next = %lu, Max = %lu, Writable = %lu.\n",do { } while(0) | |||
50 | m_start, m_end - m_start, m_end,do { } while(0) | |||
51 | m_max, m_end - m_writable)do { } while(0); | |||
52 | ||||
53 | g_assert_cmpuint(m_start, <=, m_writable)do { guint64 __n1 = (m_start), __n2 = (m_writable); if (__n1 <= __n2) ; else g_assertion_message_cmpnum ("BTE", "../src/ring.cc" , 53, ((const char*) (__PRETTY_FUNCTION__)), "m_start" " " "<=" " " "m_writable", (long double) __n1, "<=", (long double) __n2, 'i'); } while (0); | |||
54 | g_assert_cmpuint(m_writable, <=, m_end)do { guint64 __n1 = (m_writable), __n2 = (m_end); if (__n1 <= __n2) ; else g_assertion_message_cmpnum ("BTE", "../src/ring.cc" , 54, ((const char*) (__PRETTY_FUNCTION__)), "m_writable" " " "<=" " " "m_end", (long double) __n1, "<=", (long double ) __n2, 'i'); } while (0); | |||
55 | ||||
56 | g_assert_cmpuint(m_end - m_start, <=, m_max)do { guint64 __n1 = (m_end - m_start), __n2 = (m_max); if (__n1 <= __n2) ; else g_assertion_message_cmpnum ("BTE", "../src/ring.cc" , 56, ((const char*) (__PRETTY_FUNCTION__)), "m_end - m_start" " " "<=" " " "m_max", (long double) __n1, "<=", (long double ) __n2, 'i'); } while (0); | |||
57 | g_assert_cmpuint(m_end - m_writable, <=, m_mask)do { guint64 __n1 = (m_end - m_writable), __n2 = (m_mask); if (__n1 <= __n2) ; else g_assertion_message_cmpnum ("BTE", "../src/ring.cc" , 57, ((const char*) (__PRETTY_FUNCTION__)), "m_end - m_writable" " " "<=" " " "m_mask", (long double) __n1, "<=", (long double) __n2, 'i'); } while (0); | |||
58 | } | |||
59 | #else | |||
60 | #define validate(...)do { } while(0) do { } while(0) | |||
61 | #endif | |||
62 | ||||
63 | Ring::Ring(row_t max_rows, | |||
64 | bool has_streams) | |||
65 | : m_max{MAX(max_rows, 3)(((max_rows) > (3)) ? (max_rows) : (3))}, | |||
66 | m_has_streams{has_streams}, | |||
67 | m_last_attr{basic_cell.attr} | |||
68 | { | |||
69 | _bte_debug_print(BTE_DEBUG_RING, "New ring %p.\n", this)do { } while(0); | |||
70 | ||||
71 | m_array = (BteRowData* ) g_malloc0 (sizeof (m_array[0]) * (m_mask + 1)); | |||
72 | ||||
73 | if (has_streams) { | |||
74 | m_attr_stream = _bte_file_stream_new (); | |||
75 | m_text_stream = _bte_file_stream_new (); | |||
76 | m_row_stream = _bte_file_stream_new (); | |||
77 | } else { | |||
78 | m_attr_stream = m_text_stream = m_row_stream = nullptr; | |||
79 | } | |||
80 | ||||
81 | m_utf8_buffer = g_string_sized_new (128); | |||
82 | ||||
83 | _bte_row_data_init (&m_cached_row); | |||
84 | ||||
85 | m_hyperlinks = g_ptr_array_new(); | |||
86 | auto empty_str = g_string_new_len("", 0); | |||
87 | g_ptr_array_add(m_hyperlinks, empty_str); | |||
88 | ||||
89 | validate()do { } while(0); | |||
90 | } | |||
91 | ||||
92 | Ring::~Ring() | |||
93 | { | |||
94 | for (size_t i = 0; i <= m_mask; i++) | |||
95 | _bte_row_data_fini (&m_array[i]); | |||
96 | ||||
97 | g_free (m_array); | |||
98 | ||||
99 | if (m_has_streams) { | |||
100 | g_object_unref (m_attr_stream); | |||
101 | g_object_unref (m_text_stream); | |||
102 | g_object_unref (m_row_stream); | |||
103 | } | |||
104 | ||||
105 | g_string_free (m_utf8_buffer, TRUE(!(0))); | |||
106 | ||||
107 | for (size_t i = 0; i < m_hyperlinks->len; i++) | |||
108 | g_string_free (hyperlink_get(i), TRUE(!(0))); | |||
109 | g_ptr_array_free (m_hyperlinks, TRUE(!(0))); | |||
110 | ||||
111 | _bte_row_data_fini(&m_cached_row); | |||
112 | } | |||
113 | ||||
114 | #define SET_BIT(buf, n)buf[(n) / 8] |= (1 << ((n) % 8)) buf[(n) / 8] |= (1 << ((n) % 8)) | |||
115 | #define GET_BIT(buf, n)((buf[(n) / 8] >> ((n) % 8)) & 1) ((buf[(n) / 8] >> ((n) % 8)) & 1) | |||
116 | ||||
117 | /* | |||
118 | * Do a round of garbage collection. Hyperlinks that no longer occur in the ring are wiped out. | |||
119 | */ | |||
120 | void | |||
121 | Ring::hyperlink_gc() | |||
122 | { | |||
123 | row_t i, j; | |||
124 | hyperlink_idx_t idx; | |||
125 | BteRowData* row; | |||
126 | char *used; | |||
127 | ||||
128 | _bte_debug_print (BTE_DEBUG_HYPERLINK,do { } while(0) | |||
129 | "hyperlink: GC starting (highest used idx is %d)\n",do { } while(0) | |||
130 | m_hyperlink_highest_used_idx)do { } while(0); | |||
131 | ||||
132 | m_hyperlink_maybe_gc_counter = 0; | |||
133 | ||||
134 | if (m_hyperlink_highest_used_idx == 0) { | |||
135 | _bte_debug_print (BTE_DEBUG_HYPERLINK,do { } while(0) | |||
136 | "hyperlink: GC done (no links at all, nothing to do)\n")do { } while(0); | |||
137 | return; | |||
138 | } | |||
139 | ||||
140 | /* One bit for each idx to see if it's used. */ | |||
141 | used = (char *) g_malloc0 (m_hyperlink_highest_used_idx / 8 + 1); | |||
142 | ||||
143 | /* A few special values not to be garbage collected. */ | |||
144 | SET_BIT(used, m_hyperlink_current_idx)used[(m_hyperlink_current_idx) / 8] |= (1 << ((m_hyperlink_current_idx ) % 8)); | |||
145 | SET_BIT(used, m_hyperlink_hover_idx)used[(m_hyperlink_hover_idx) / 8] |= (1 << ((m_hyperlink_hover_idx ) % 8)); | |||
146 | SET_BIT(used, m_last_attr.hyperlink_idx)used[(m_last_attr.hyperlink_idx) / 8] |= (1 << ((m_last_attr .hyperlink_idx) % 8)); | |||
147 | ||||
148 | for (i = m_writable; i < m_end; i++) { | |||
149 | row = get_writable_index(i); | |||
150 | for (j = 0; j < row->len; j++) { | |||
151 | idx = row->cells[j].attr.hyperlink_idx; | |||
152 | SET_BIT(used, idx)used[(idx) / 8] |= (1 << ((idx) % 8)); | |||
153 | } | |||
154 | } | |||
155 | ||||
156 | for (idx = 1; idx <= m_hyperlink_highest_used_idx; idx++) { | |||
157 | if (!GET_BIT(used, idx)((used[(idx) / 8] >> ((idx) % 8)) & 1) && hyperlink_get(idx)->len != 0) { | |||
158 | _bte_debug_print (BTE_DEBUG_HYPERLINK,do { } while(0) | |||
159 | "hyperlink: GC purging link %d to id;uri=\"%s\"\n",do { } while(0) | |||
160 | idx, hyperlink_get(idx)->str)do { } while(0); | |||
161 | /* Wipe out the ID and URI itself so it doesn't linger on in the memory for a long time */ | |||
162 | memset(hyperlink_get(idx)->str, 0, hyperlink_get(idx)->len); | |||
163 | g_string_truncate (hyperlink_get(idx), 0)g_string_truncate_inline (hyperlink_get(idx), 0); | |||
164 | } | |||
165 | } | |||
166 | ||||
167 | while (m_hyperlink_highest_used_idx >= 1 && hyperlink_get(m_hyperlink_highest_used_idx)->len == 0) { | |||
168 | m_hyperlink_highest_used_idx--; | |||
169 | } | |||
170 | ||||
171 | _bte_debug_print (BTE_DEBUG_HYPERLINK,do { } while(0) | |||
172 | "hyperlink: GC done (highest used idx is now %d)\n",do { } while(0) | |||
173 | m_hyperlink_highest_used_idx)do { } while(0); | |||
174 | ||||
175 | g_free (used); | |||
176 | } | |||
177 | ||||
178 | /* | |||
179 | * Cumulate the given value, and do a GC when 65536 is reached. | |||
180 | */ | |||
181 | void | |||
182 | Ring::hyperlink_maybe_gc(row_t increment) | |||
183 | { | |||
184 | m_hyperlink_maybe_gc_counter += increment; | |||
185 | ||||
186 | _bte_debug_print (BTE_DEBUG_HYPERLINK,do { } while(0) | |||
187 | "hyperlink: maybe GC, counter at %ld\n",do { } while(0) | |||
188 | m_hyperlink_maybe_gc_counter)do { } while(0); | |||
189 | ||||
190 | if (m_hyperlink_maybe_gc_counter >= 65536) | |||
191 | hyperlink_gc(); | |||
192 | } | |||
193 | ||||
194 | /* | |||
195 | * Find existing idx for the hyperlink or allocate a new one. | |||
196 | * | |||
197 | * Returns 0 if given no hyperlink or an empty one, or if the pool is full. | |||
198 | * Returns the idx (either already existing or newly allocated) from 1 up to | |||
199 | * BTE_HYPERLINK_COUNT_MAX inclusive otherwise. | |||
200 | * | |||
201 | * FIXME do something more effective than a linear search | |||
202 | */ | |||
203 | Ring::hyperlink_idx_t | |||
204 | Ring::get_hyperlink_idx_no_update_current(char const* hyperlink) | |||
205 | { | |||
206 | hyperlink_idx_t idx; | |||
207 | gsize len; | |||
208 | GString *str; | |||
209 | ||||
210 | if (!hyperlink || !hyperlink[0]) | |||
211 | return 0; | |||
212 | ||||
213 | len = strlen(hyperlink); | |||
214 | ||||
215 | /* Linear search for this particular URI */ | |||
216 | auto const last_idx = m_hyperlink_highest_used_idx + 1; | |||
217 | for (idx = 1; idx < last_idx; ++idx) { | |||
218 | if (strcmp(hyperlink_get(idx)->str, hyperlink) == 0) { | |||
219 | _bte_debug_print (BTE_DEBUG_HYPERLINK,do { } while(0) | |||
220 | "get_hyperlink_idx: already existing idx %d for id;uri=\"%s\"\n",do { } while(0) | |||
221 | idx, hyperlink)do { } while(0); | |||
222 | return idx; | |||
223 | } | |||
224 | } | |||
225 | ||||
226 | /* FIXME it's the second time we're GCing if coming from get_hyperlink_idx */ | |||
227 | hyperlink_gc(); | |||
228 | ||||
229 | /* Another linear search for an empty slot where a GString is already allocated */ | |||
230 | for (idx = 1; idx < m_hyperlinks->len; idx++) { | |||
231 | if (hyperlink_get(idx)->len == 0) { | |||
232 | _bte_debug_print (BTE_DEBUG_HYPERLINK,do { } while(0) | |||
233 | "get_hyperlink_idx: reassigning old idx %d for id;uri=\"%s\"\n",do { } while(0) | |||
234 | idx, hyperlink)do { } while(0); | |||
235 | /* Grow size if required, however, never shrink to avoid long-term memory fragmentation. */ | |||
236 | g_string_append_len (hyperlink_get(idx), hyperlink, len)g_string_append_len_inline (hyperlink_get(idx), hyperlink, len ); | |||
237 | m_hyperlink_highest_used_idx = MAX (m_hyperlink_highest_used_idx, idx)(((m_hyperlink_highest_used_idx) > (idx)) ? (m_hyperlink_highest_used_idx ) : (idx)); | |||
238 | return idx; | |||
239 | } | |||
240 | } | |||
241 | ||||
242 | /* All allocated slots are in use. Gotta allocate a new one */ | |||
243 | g_assert_cmpuint(m_hyperlink_highest_used_idx + 1, ==, m_hyperlinks->len)do { guint64 __n1 = (m_hyperlink_highest_used_idx + 1), __n2 = (m_hyperlinks->len); if (__n1 == __n2) ; else g_assertion_message_cmpnum ("BTE", "../src/ring.cc", 243, ((const char*) (__PRETTY_FUNCTION__ )), "m_hyperlink_highest_used_idx + 1" " " "==" " " "m_hyperlinks->len" , (long double) __n1, "==", (long double) __n2, 'i'); } while (0); | |||
244 | ||||
245 | /* BTE_HYPERLINK_COUNT_MAX should be big enough for this not to happen under | |||
246 | normal circumstances. Anyway, it's cheap to protect against extreme ones. */ | |||
247 | if (m_hyperlink_highest_used_idx == BTE_HYPERLINK_COUNT_MAX((1 << 20) - 2)) { | |||
248 | _bte_debug_print (BTE_DEBUG_HYPERLINK,do { } while(0) | |||
249 | "get_hyperlink_idx: idx 0 (ran out of available idxs) for id;uri=\"%s\"\n",do { } while(0) | |||
250 | hyperlink)do { } while(0); | |||
251 | return 0; | |||
252 | } | |||
253 | ||||
254 | idx = ++m_hyperlink_highest_used_idx; | |||
255 | _bte_debug_print (BTE_DEBUG_HYPERLINK,do { } while(0) | |||
256 | "get_hyperlink_idx: brand new idx %d for id;uri=\"%s\"\n",do { } while(0) | |||
257 | idx, hyperlink)do { } while(0); | |||
258 | str = g_string_new_len (hyperlink, len); | |||
259 | g_ptr_array_add(m_hyperlinks, str); | |||
260 | ||||
261 | g_assert_cmpuint(m_hyperlink_highest_used_idx + 1, ==, m_hyperlinks->len)do { guint64 __n1 = (m_hyperlink_highest_used_idx + 1), __n2 = (m_hyperlinks->len); if (__n1 == __n2) ; else g_assertion_message_cmpnum ("BTE", "../src/ring.cc", 261, ((const char*) (__PRETTY_FUNCTION__ )), "m_hyperlink_highest_used_idx + 1" " " "==" " " "m_hyperlinks->len" , (long double) __n1, "==", (long double) __n2, 'i'); } while (0); | |||
262 | ||||
263 | return idx; | |||
264 | } | |||
265 | ||||
266 | /* | |||
267 | * Find existing idx for the hyperlink or allocate a new one. | |||
268 | * | |||
269 | * Returns 0 if given no hyperlink or an empty one, or if the pool is full. | |||
270 | * Returns the idx (either already existing or newly allocated) from 1 up to | |||
271 | * BTE_HYPERLINK_COUNT_MAX inclusive otherwise. | |||
272 | * | |||
273 | * The current idx is also updated, in order not to be garbage collected. | |||
274 | */ | |||
275 | Ring::hyperlink_idx_t | |||
276 | Ring::get_hyperlink_idx(char const* hyperlink) | |||
277 | { | |||
278 | /* Release current idx and do a round of GC to possibly purge its hyperlink, | |||
279 | * even if new hyperlink is nullptr or empty. */ | |||
280 | m_hyperlink_current_idx = 0; | |||
281 | hyperlink_gc(); | |||
282 | ||||
283 | m_hyperlink_current_idx = get_hyperlink_idx_no_update_current(hyperlink); | |||
284 | return m_hyperlink_current_idx; | |||
285 | } | |||
286 | ||||
287 | void | |||
288 | Ring::freeze_row(row_t position, | |||
289 | BteRowData const* row) | |||
290 | { | |||
291 | BteCell *cell; | |||
292 | GString *buffer = m_utf8_buffer; | |||
293 | GString *hyperlink; | |||
294 | int i; | |||
295 | gboolean froze_hyperlink = FALSE(0); | |||
296 | ||||
297 | _bte_debug_print (BTE_DEBUG_RING, "Freezing row %lu.\n", position)do { } while(0); | |||
298 | ||||
299 | g_assert(m_has_streams)do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_48 ; if (m_has_streams) _g_boolean_var_48 = 1; else _g_boolean_var_48 = 0; _g_boolean_var_48; }), 1)) ; else g_assertion_message_expr ("BTE", "../src/ring.cc", 299, ((const char*) (__PRETTY_FUNCTION__ )), "m_has_streams"); } while (0); | |||
300 | ||||
301 | RowRecord record; | |||
302 | memset(&record, 0, sizeof(record)); | |||
303 | record.text_start_offset = _bte_stream_head(m_text_stream); | |||
304 | record.attr_start_offset = _bte_stream_head(m_attr_stream); | |||
305 | record.is_ascii = 1; | |||
306 | ||||
307 | g_string_set_size (buffer, 0); | |||
308 | for (i = 0, cell = row->cells; i < row->len; i++, cell++) { | |||
309 | BteCellAttr attr; | |||
310 | int num_chars; | |||
311 | ||||
312 | /* Attr storage: | |||
313 | * | |||
314 | * 1. We don't store attrs for fragments. They can be | |||
315 | * reconstructed using the columns of their start cell. | |||
316 | * | |||
317 | * 2. We store one attr per bteunistr character starting | |||
318 | * from the second character, with columns=0. | |||
319 | * | |||
320 | * That's enough to reconstruct the attrs, and to store | |||
321 | * the text in real UTF-8. | |||
322 | */ | |||
323 | attr = cell->attr; | |||
324 | if (G_LIKELY (!attr.fragment())(__builtin_expect (__extension__ ({ int _g_boolean_var_49; if (!attr.fragment()) _g_boolean_var_49 = 1; else _g_boolean_var_49 = 0; _g_boolean_var_49; }), 1))) { | |||
325 | CellAttrChange attr_change; | |||
326 | guint16 hyperlink_length; | |||
327 | ||||
328 | if (memcmp(&m_last_attr, &attr, sizeof (BteCellAttr)) != 0) { | |||
329 | m_last_attr_text_start_offset = record.text_start_offset + buffer->len; | |||
330 | memset(&attr_change, 0, sizeof (attr_change)); | |||
331 | attr_change.text_end_offset = m_last_attr_text_start_offset; | |||
332 | _attrcpy(&attr_change.attr, &m_last_attr); | |||
333 | hyperlink = hyperlink_get(m_last_attr.hyperlink_idx); | |||
334 | attr_change.attr.hyperlink_length = hyperlink->len; | |||
335 | _bte_stream_append (m_attr_stream, (char const* ) &attr_change, sizeof (attr_change)); | |||
336 | if (G_UNLIKELY (hyperlink->len != 0)(__builtin_expect (__extension__ ({ int _g_boolean_var_50; if (hyperlink->len != 0) _g_boolean_var_50 = 1; else _g_boolean_var_50 = 0; _g_boolean_var_50; }), 0))) { | |||
337 | _bte_stream_append (m_attr_stream, hyperlink->str, hyperlink->len); | |||
338 | froze_hyperlink = TRUE(!(0)); | |||
339 | } | |||
340 | hyperlink_length = attr_change.attr.hyperlink_length; | |||
341 | _bte_stream_append (m_attr_stream, (char const* ) &hyperlink_length, 2); | |||
342 | if (!buffer->len) | |||
343 | /* This row doesn't use last_attr, adjust */ | |||
344 | record.attr_start_offset += sizeof (attr_change) + hyperlink_length + 2; | |||
345 | m_last_attr = attr; | |||
346 | } | |||
347 | ||||
348 | num_chars = _bte_unistr_strlen (cell->c); | |||
349 | if (num_chars > 1) { | |||
350 | /* Combining chars */ | |||
351 | attr.set_columns(0); | |||
352 | m_last_attr_text_start_offset = record.text_start_offset + buffer->len | |||
353 | + g_unichar_to_utf8 (_bte_unistr_get_base (cell->c), nullptr); | |||
354 | memset(&attr_change, 0, sizeof (attr_change)); | |||
355 | attr_change.text_end_offset = m_last_attr_text_start_offset; | |||
356 | _attrcpy(&attr_change.attr, &m_last_attr); | |||
357 | hyperlink = hyperlink_get(m_last_attr.hyperlink_idx); | |||
358 | attr_change.attr.hyperlink_length = hyperlink->len; | |||
359 | _bte_stream_append (m_attr_stream, (char const* ) &attr_change, sizeof (attr_change)); | |||
360 | if (G_UNLIKELY (hyperlink->len != 0)(__builtin_expect (__extension__ ({ int _g_boolean_var_51; if (hyperlink->len != 0) _g_boolean_var_51 = 1; else _g_boolean_var_51 = 0; _g_boolean_var_51; }), 0))) { | |||
361 | _bte_stream_append (m_attr_stream, hyperlink->str, hyperlink->len); | |||
362 | froze_hyperlink = TRUE(!(0)); | |||
363 | } | |||
364 | hyperlink_length = attr_change.attr.hyperlink_length; | |||
365 | _bte_stream_append (m_attr_stream, (char const* ) &hyperlink_length, 2); | |||
366 | m_last_attr = attr; | |||
367 | } | |||
368 | ||||
369 | if (cell->c < 32 || cell->c > 126) record.is_ascii = 0; | |||
370 | _bte_unistr_append_to_string (cell->c, buffer); | |||
371 | } | |||
372 | } | |||
373 | if (!row->attr.soft_wrapped) | |||
374 | g_string_append_c (buffer, '\n')g_string_append_c_inline (buffer, '\n'); | |||
375 | record.soft_wrapped = row->attr.soft_wrapped; | |||
376 | record.bidi_flags = row->attr.bidi_flags; | |||
377 | ||||
378 | _bte_stream_append(m_text_stream, buffer->str, buffer->len); | |||
379 | append_row_record(&record, position); | |||
380 | ||||
381 | /* After freezing some hyperlinks, do a hyperlink GC. The constant is totally arbitrary, feel free to fine tune. */ | |||
382 | if (froze_hyperlink) | |||
383 | hyperlink_maybe_gc(1024); | |||
384 | } | |||
385 | ||||
386 | /* If do_truncate (data is placed back from the stream to the ring), real new hyperlink idxs are looked up or allocated. | |||
387 | * | |||
388 | * If !do_truncate (data is fetched only to be displayed), hyperlinked cells are given the pseudo idx BTE_HYPERLINK_IDX_TARGET_IN_STREAM, | |||
389 | * except for the hyperlink_hover_idx which gets this real idx. This is important for hover underlining. | |||
390 | * | |||
391 | * Optionally updates the hyperlink parameter to point to the ring-owned hyperlink target. */ | |||
392 | void | |||
393 | Ring::thaw_row(row_t position, | |||
394 | BteRowData* row, | |||
395 | bool do_truncate, | |||
396 | int hyperlink_column, | |||
397 | char const** hyperlink) | |||
398 | { | |||
399 | RowRecord records[2], record; | |||
400 | BteCellAttr attr; | |||
401 | CellAttrChange attr_change; | |||
402 | BteCell cell; | |||
403 | char const* p, *q, *end; | |||
404 | GString *buffer = m_utf8_buffer; | |||
405 | char hyperlink_readbuf[BTE_HYPERLINK_TOTAL_LENGTH_MAX(250 + 1 + 2083) + 1]; | |||
406 | ||||
407 | hyperlink_readbuf[0] = '\0'; | |||
408 | if (hyperlink) { | |||
409 | m_hyperlink_buf[0] = '\0'; | |||
410 | *hyperlink = m_hyperlink_buf; | |||
411 | } | |||
412 | ||||
413 | _bte_debug_print (BTE_DEBUG_RING, "Thawing row %lu.\n", position)do { } while(0); | |||
414 | ||||
415 | g_assert(m_has_streams)do { if (__builtin_expect (__extension__ ({ int _g_boolean_var_52 ; if (m_has_streams) _g_boolean_var_52 = 1; else _g_boolean_var_52 = 0; _g_boolean_var_52; }), 1)) ; else g_assertion_message_expr ("BTE", "../src/ring.cc", 415, ((const char*) (__PRETTY_FUNCTION__ )), "m_has_streams"); } while (0); | |||
416 | ||||
417 | _bte_row_data_clear (row); | |||
418 | ||||
419 | attr_change.text_end_offset = 0; | |||
420 | ||||
421 | if (!read_row_record(&records[0], position)) | |||
422 | return; | |||
423 | if ((position + 1) * sizeof (records[0]) < _bte_stream_head (m_row_stream)) { | |||
424 | if (!read_row_record(&records[1], position + 1)) | |||
425 | return; | |||
426 | } else | |||
427 | records[1].text_start_offset = _bte_stream_head (m_text_stream); | |||
428 | ||||
429 | g_string_set_size (buffer, records[1].text_start_offset - records[0].text_start_offset); | |||
430 | if (!_bte_stream_read (m_text_stream, records[0].text_start_offset, buffer->str, buffer->len)) | |||
431 | return; | |||
432 | ||||
433 | record = records[0]; | |||
434 | ||||
435 | if (G_LIKELY (buffer->len && buffer->str[buffer->len - 1] == '\n')(__builtin_expect (__extension__ ({ int _g_boolean_var_53; if (buffer->len && buffer->str[buffer->len - 1 ] == '\n') _g_boolean_var_53 = 1; else _g_boolean_var_53 = 0; _g_boolean_var_53; }), 1))) | |||
436 | g_string_truncate (buffer, buffer->len - 1)g_string_truncate_inline (buffer, buffer->len - 1); | |||
437 | else | |||
438 | row->attr.soft_wrapped = TRUE(!(0)); | |||
439 | row->attr.bidi_flags = records[0].bidi_flags; | |||
440 | ||||
441 | p = buffer->str; | |||
442 | end = p + buffer->len; | |||
443 | while (p < end) { | |||
444 | if (record.text_start_offset >= m_last_attr_text_start_offset) { | |||
445 | attr = m_last_attr; | |||
446 | strcpy(hyperlink_readbuf, hyperlink_get(attr.hyperlink_idx)->str); | |||
447 | } else { | |||
448 | if (record.text_start_offset >= attr_change.text_end_offset) { | |||
449 | if (!_bte_stream_read (m_attr_stream, record.attr_start_offset, (char *) &attr_change, sizeof (attr_change))) | |||
450 | return; | |||
451 | record.attr_start_offset += sizeof (attr_change); | |||
452 | g_assert_cmpuint (attr_change.attr.hyperlink_length, <=, BTE_HYPERLINK_TOTAL_LENGTH_MAX)do { guint64 __n1 = (attr_change.attr.hyperlink_length), __n2 = ((250 + 1 + 2083)); if (__n1 <= __n2) ; else g_assertion_message_cmpnum ("BTE", "../src/ring.cc", 452, ((const char*) (__PRETTY_FUNCTION__ )), "attr_change.attr.hyperlink_length" " " "<=" " " "BTE_HYPERLINK_TOTAL_LENGTH_MAX" , (long double) __n1, "<=", (long double) __n2, 'i'); } while (0); | |||
453 | if (attr_change.attr.hyperlink_length && !_bte_stream_read (m_attr_stream, record.attr_start_offset, hyperlink_readbuf, attr_change.attr.hyperlink_length)) | |||
454 | return; | |||
455 | hyperlink_readbuf[attr_change.attr.hyperlink_length] = '\0'; | |||
456 | record.attr_start_offset += attr_change.attr.hyperlink_length + 2; | |||
457 | ||||
458 | _attrcpy(&attr, &attr_change.attr); | |||
459 | attr.hyperlink_idx = 0; | |||
460 | if (G_UNLIKELY (attr_change.attr.hyperlink_length)(__builtin_expect (__extension__ ({ int _g_boolean_var_54; if (attr_change.attr.hyperlink_length) _g_boolean_var_54 = 1; else _g_boolean_var_54 = 0; _g_boolean_var_54; }), 0))) { | |||
461 | if (do_truncate) { | |||
462 | /* Find the existing idx or allocate a new one, just as when receiving an OSC 8 escape sequence. | |||
463 | * Do not update the current idx though. */ | |||
464 | attr.hyperlink_idx = get_hyperlink_idx_no_update_current(hyperlink_readbuf); | |||
465 | } else { | |||
466 | /* Use a special hyperlink idx, except if to be underlined because the hyperlink is the same as the hovered cell's. */ | |||
467 | attr.hyperlink_idx = BTE_HYPERLINK_IDX_TARGET_IN_STREAM(((1 << 20) - 2) + 1); | |||
468 | if (m_hyperlink_hover_idx != 0 && strcmp(hyperlink_readbuf, hyperlink_get(m_hyperlink_hover_idx)->str) == 0) { | |||
469 | /* FIXME here we're calling the expensive strcmp() above and get_hyperlink_idx_no_update_current() way too many times. */ | |||
470 | attr.hyperlink_idx = get_hyperlink_idx_no_update_current(hyperlink_readbuf); | |||
471 | } | |||
472 | } | |||
473 | } | |||
474 | } | |||
475 | } | |||
476 | ||||
477 | cell.attr = attr; | |||
478 | _BTE_DEBUG_IF(BTE_DEBUG_RING | BTE_DEBUG_HYPERLINK)if (0) { | |||
479 | /* Debug: Reverse the colors for the stream's contents. */ | |||
480 | if (!do_truncate) { | |||
481 | cell.attr.attr ^= BTE_ATTR_REVERSE(1U << ((((((((0) + (4)) + (1)) + (1)) + (1)) + (2)) + ( 1)) + (1))); | |||
482 | } | |||
483 | } | |||
484 | cell.c = g_utf8_get_char (p); | |||
485 | ||||
486 | q = g_utf8_next_char (p)(char *)((p) + g_utf8_skip[*(const guchar *)(p)]); | |||
487 | record.text_start_offset += q - p; | |||
488 | p = q; | |||
489 | ||||
490 | if (G_UNLIKELY (cell.attr.columns() == 0)(__builtin_expect (__extension__ ({ int _g_boolean_var_55; if (cell.attr.columns() == 0) _g_boolean_var_55 = 1; else _g_boolean_var_55 = 0; _g_boolean_var_55; }), 0))) { | |||
491 | if (G_LIKELY (row->len)(__builtin_expect (__extension__ ({ int _g_boolean_var_56; if (row->len) _g_boolean_var_56 = 1; else _g_boolean_var_56 = 0; _g_boolean_var_56; }), 1))) { | |||
492 | /* Combine it */ | |||
493 | row->cells[row->len - 1].c = _bte_unistr_append_unichar (row->cells[row->len - 1].c, cell.c); | |||
494 | /* Spread it to all the previous cells of a potentially multicell character */ | |||
495 | for (int i = row->len - 1; i >= 1 && row->cells[i].attr.fragment(); i--) { | |||
496 | row->cells[i - 1].c = row->cells[i].c; | |||
497 | } | |||
498 | } else { | |||
499 | cell.attr.set_columns(1); | |||
500 | if (row->len == hyperlink_column && hyperlink != nullptr) | |||
501 | *hyperlink = strcpy(m_hyperlink_buf, hyperlink_readbuf); | |||
502 | _bte_row_data_append (row, &cell); | |||
503 | } | |||
504 | } else { | |||
505 | if (row->len == hyperlink_column && hyperlink != nullptr) | |||
506 | *hyperlink = strcpy(m_hyperlink_buf, hyperlink_readbuf); | |||
507 | _bte_row_data_append (row, &cell); | |||
508 | if (cell.attr.columns() > 1) { | |||
509 | /* Add the fragments */ | |||
510 | int i, columns = cell.attr.columns(); | |||
511 | cell.attr.set_fragment(true); | |||
512 | cell.attr.set_columns(1); | |||
513 | for (i = 1; i < columns; i++) { | |||
514 | if (row->len == hyperlink_column && hyperlink != nullptr) | |||
515 | *hyperlink = strcpy(m_hyperlink_buf, hyperlink_readbuf); | |||
516 | _bte_row_data_append (row, &cell); | |||
517 | } | |||
518 | } | |||
519 | } | |||
520 | } | |||
521 | ||||
522 | /* FIXME this is extremely complicated (by design), figure out something better. | |||
523 | This is the only place where we need to walk backwards in attr_stream, | |||
524 | which is the reason for the hyperlink's length being repeated after the hyperlink itself. */ | |||
525 | if (do_truncate) { | |||
526 | gsize attr_stream_truncate_at = records[0].attr_start_offset; | |||
527 | _bte_debug_print (BTE_DEBUG_RING, "Truncating\n")do { } while(0); | |||
528 | if (records[0].text_start_offset <= m_last_attr_text_start_offset) { | |||
529 | /* Check the previous attr record. If its text ends where truncating, this attr record also needs to be removed. */ | |||
530 | guint16 hyperlink_length; | |||
531 | if (_bte_stream_read (m_attr_stream, attr_stream_truncate_at - 2, (char *) &hyperlink_length, 2)) { | |||
532 | g_assert_cmpuint (hyperlink_length, <=, BTE_HYPERLINK_TOTAL_LENGTH_MAX)do { guint64 __n1 = (hyperlink_length), __n2 = ((250 + 1 + 2083 )); if (__n1 <= __n2) ; else g_assertion_message_cmpnum ("BTE" , "../src/ring.cc", 532, ((const char*) (__PRETTY_FUNCTION__) ), "hyperlink_length" " " "<=" " " "BTE_HYPERLINK_TOTAL_LENGTH_MAX" , (long double) __n1, "<=", (long double) __n2, 'i'); } while (0); | |||
533 | if (_bte_stream_read (m_attr_stream, attr_stream_truncate_at - 2 - hyperlink_length - sizeof (attr_change), (char *) &attr_change, sizeof (attr_change))) { | |||
534 | if (records[0].text_start_offset == attr_change.text_end_offset) { | |||
535 | _bte_debug_print (BTE_DEBUG_RING, "... at attribute change\n")do { } while(0); | |||
536 | attr_stream_truncate_at -= sizeof (attr_change) + hyperlink_length + 2; | |||
537 | } | |||
538 | } | |||
539 | } | |||
540 | /* Reconstruct last_attr from the first record of attr_stream that we cut off, | |||
541 | last_attr_text_start_offset from the last record that we keep. */ | |||
542 | if (_bte_stream_read (m_attr_stream, attr_stream_truncate_at, (char *) &attr_change, sizeof (attr_change))) { | |||
543 | _attrcpy(&m_last_attr, &attr_change.attr); | |||
544 | m_last_attr.hyperlink_idx = 0; | |||
545 | if (attr_change.attr.hyperlink_length && _bte_stream_read (m_attr_stream, attr_stream_truncate_at + sizeof (attr_change), (char *) &hyperlink_readbuf, attr_change.attr.hyperlink_length)) { | |||
546 | hyperlink_readbuf[attr_change.attr.hyperlink_length] = '\0'; | |||
547 | m_last_attr.hyperlink_idx = get_hyperlink_idx(hyperlink_readbuf); | |||
548 | } | |||
549 | if (_bte_stream_read (m_attr_stream, attr_stream_truncate_at - 2, (char *) &hyperlink_length, 2)) { | |||
550 | g_assert_cmpuint (hyperlink_length, <=, BTE_HYPERLINK_TOTAL_LENGTH_MAX)do { guint64 __n1 = (hyperlink_length), __n2 = ((250 + 1 + 2083 )); if (__n1 <= __n2) ; else g_assertion_message_cmpnum ("BTE" , "../src/ring.cc", 550, ((const char*) (__PRETTY_FUNCTION__) ), "hyperlink_length" " " "<=" " " "BTE_HYPERLINK_TOTAL_LENGTH_MAX" , (long double) __n1, "<=", (long double) __n2, 'i'); } while (0); | |||
551 | if (_bte_stream_read (m_attr_stream, attr_stream_truncate_at - 2 - hyperlink_length - sizeof (attr_change), (char *) &attr_change, sizeof (attr_change))) { | |||
552 | m_last_attr_text_start_offset = attr_change.text_end_offset; | |||
553 | } else { | |||
554 | m_last_attr_text_start_offset = 0; | |||
555 | } | |||
556 | } else { | |||
557 | m_last_attr_text_start_offset = 0; | |||
558 | } | |||
559 | } else { | |||
560 | m_last_attr_text_start_offset = 0; | |||
561 | m_last_attr = basic_cell.attr; | |||
562 | } | |||
563 | } | |||
564 | _bte_stream_truncate (m_row_stream, position * sizeof (record)); | |||
565 | _bte_stream_truncate (m_attr_stream, attr_stream_truncate_at); | |||
566 | _bte_stream_truncate (m_text_stream, records[0].text_start_offset); | |||
567 | } | |||
568 | } | |||
569 | ||||
570 | void | |||
571 | Ring::reset_streams(row_t position) | |||
572 | { | |||
573 | _bte_debug_print (BTE_DEBUG_RING, "Reseting streams to %lu.\n", position)do { } while(0); | |||
574 | ||||
575 | if (m_has_streams) { | |||
576 | _bte_stream_reset(m_row_stream, position * sizeof(RowRecord)); | |||
577 | _bte_stream_reset(m_text_stream, _bte_stream_head(m_text_stream)); | |||
578 | _bte_stream_reset(m_attr_stream, _bte_stream_head(m_attr_stream)); | |||
579 | } | |||
580 | ||||
581 | m_last_attr_text_start_offset = 0; | |||
582 | m_last_attr = basic_cell.attr; | |||
583 | } | |||
584 | ||||
585 | Ring::row_t | |||
586 | Ring::reset() | |||
587 | { | |||
588 | _bte_debug_print (BTE_DEBUG_RING, "Reseting the ring at %lu.\n", m_end)do { } while(0); | |||
589 | ||||
590 | reset_streams(m_end); | |||
591 | m_start = m_writable = m_end; | |||
592 | m_cached_row_num = (row_t)-1; | |||
593 | ||||
594 | return m_end; | |||
595 | } | |||
596 | ||||
597 | BteRowData const* | |||
598 | Ring::index(row_t position) | |||
599 | { | |||
600 | if (G_LIKELY (position >= m_writable)(__builtin_expect (__extension__ ({ int _g_boolean_var_57; if (position >= m_writable) _g_boolean_var_57 = 1; else _g_boolean_var_57 = 0; _g_boolean_var_57; }), 1))) | |||
601 | return get_writable_index(position); | |||
602 | ||||
603 | if (m_cached_row_num != position) { | |||
604 | _bte_debug_print(BTE_DEBUG_RING, "Caching row %lu.\n", position)do { } while(0); | |||
605 | thaw_row(position, &m_cached_row, false, -1, nullptr); | |||
606 | m_cached_row_num = position; | |||
607 | } | |||
608 | ||||
609 | return &m_cached_row; | |||
610 | } | |||
611 | ||||
612 | bool | |||
613 | Ring::is_soft_wrapped(row_t position) | |||
614 | { | |||
615 | const BteRowData *row; | |||
616 | RowRecord record; | |||
617 | ||||
618 | if (G_UNLIKELY (position < m_start || position >= m_end)(__builtin_expect (__extension__ ({ int _g_boolean_var_58; if (position < m_start || position >= m_end) _g_boolean_var_58 = 1; else _g_boolean_var_58 = 0; _g_boolean_var_58; }), 0))) | |||
619 | return false; | |||
620 | ||||
621 | if (G_LIKELY (position >= m_writable)(__builtin_expect (__extension__ ({ int _g_boolean_var_59; if (position >= m_writable) _g_boolean_var_59 = 1; else _g_boolean_var_59 = 0; _g_boolean_var_59; }), 1))) { | |||
622 | row = get_writable_index(position); | |||
623 | return row->attr.soft_wrapped; | |||
624 | } | |||
625 | ||||
626 | /* The row is scrolled out to the stream. Save work by not reading the actual row. | |||
627 | * The requested information is readily available in row_stream, too. */ | |||
628 | if (G_UNLIKELY (!read_row_record(&record, position))(__builtin_expect (__extension__ ({ int _g_boolean_var_60; if (!read_row_record(&record, position)) _g_boolean_var_60 = 1; else _g_boolean_var_60 = 0; _g_boolean_var_60; }), 0))) | |||
629 | return false; | |||
630 | return record.soft_wrapped; | |||
631 | } | |||
632 | ||||
633 | /* | |||
634 | * Returns the hyperlink idx at the given position. | |||
635 | * | |||
636 | * Updates the hyperlink parameter to point to the hyperlink's target. | |||
637 | * The buffer is owned by the ring and must not be modified by the caller. | |||
638 | * | |||
639 | * Optionally also updates the internal concept of the hovered idx. In this case, | |||
640 | * a real idx is looked up or newly allocated in the hyperlink pool even if the | |||
641 | * cell is scrolled out to the streams. | |||
642 | * This is to be able to underline all cells that share the same hyperlink. | |||
643 | * | |||
644 | * Otherwise cells from the stream might get the pseudo idx BTE_HYPERLINK_IDX_TARGET_IN_STREAM. | |||
645 | */ | |||
646 | Ring::hyperlink_idx_t | |||
647 | Ring::get_hyperlink_at_position(row_t position, | |||
648 | column_t col, | |||
649 | bool update_hover_idx, | |||
650 | char const** hyperlink) | |||
651 | { | |||
652 | hyperlink_idx_t idx; | |||
653 | char const* hp; | |||
654 | ||||
655 | if (hyperlink == nullptr) | |||
| ||||
656 | hyperlink = &hp; | |||
657 | *hyperlink = nullptr; | |||
658 | ||||
659 | if (update_hover_idx) { | |||
660 | /* Invalidate the cache because new hover idx might result in new idxs to report. */ | |||
661 | m_cached_row_num = (row_t)-1; | |||
662 | } | |||
663 | ||||
664 | if (G_UNLIKELY (!contains(position) || col < 0)(__builtin_expect (__extension__ ({ int _g_boolean_var_61; if (!contains(position) || col < 0) _g_boolean_var_61 = 1; else _g_boolean_var_61 = 0; _g_boolean_var_61; }), 0))) { | |||
665 | if (update_hover_idx) | |||
666 | m_hyperlink_hover_idx = 0; | |||
667 | return 0; | |||
668 | } | |||
669 | ||||
670 | if (G_LIKELY (position >= m_writable)(__builtin_expect (__extension__ ({ int _g_boolean_var_62; if (position >= m_writable) _g_boolean_var_62 = 1; else _g_boolean_var_62 = 0; _g_boolean_var_62; }), 1))) { | |||
671 | BteRowData* row = get_writable_index(position); | |||
672 | if (col >= _bte_row_data_length(row)((row)->len + 0)) { | |||
673 | if (update_hover_idx) | |||
674 | m_hyperlink_hover_idx = 0; | |||
675 | return 0; | |||
676 | } | |||
677 | *hyperlink = hyperlink_get(row->cells[col].attr.hyperlink_idx)->str; | |||
678 | idx = row->cells[col].attr.hyperlink_idx; | |||
679 | } else { | |||
680 | thaw_row(position, &m_cached_row, false, col, hyperlink); | |||
681 | /* Note: Intentionally don't set cached_row_num. We're about to update | |||
682 | * m_hyperlink_hover_idx which makes some idxs no longer valid. */ | |||
683 | idx = get_hyperlink_idx_no_update_current(*hyperlink); | |||
684 | } | |||
685 | if (**hyperlink == '\0') | |||
| ||||
686 | *hyperlink = nullptr; | |||
687 | if (update_hover_idx) | |||
688 | m_hyperlink_hover_idx = idx; | |||
689 | return idx; | |||
690 | } | |||
691 | ||||
692 | BteRowData* | |||
693 | Ring::index_writable(row_t position) | |||
694 | { | |||
695 | ensure_writable(position); | |||
696 | return get_writable_index(position); | |||
697 | } | |||
698 | ||||
699 | void | |||
700 | Ring::freeze_one_row() | |||
701 | { | |||
702 | BteRowData* row; | |||
703 | ||||
704 | if (G_UNLIKELY (m_writable == m_start)(__builtin_expect (__extension__ ({ int _g_boolean_var_63; if (m_writable == m_start) _g_boolean_var_63 = 1; else _g_boolean_var_63 = 0; _g_boolean_var_63; }), 0))) | |||
705 | reset_streams(m_writable); | |||
706 | ||||
707 | row = get_writable_index(m_writable); | |||
708 | freeze_row(m_writable, row); | |||
709 | ||||
710 | m_writable++; | |||
711 | } | |||
712 | ||||
713 | void | |||
714 | Ring::thaw_one_row() | |||
715 | { | |||
716 | BteRowData* row; | |||
717 | ||||
718 | g_assert_cmpuint(m_start, <, m_writable)do { guint64 __n1 = (m_start), __n2 = (m_writable); if (__n1 < __n2) ; else g_assertion_message_cmpnum ("BTE", "../src/ring.cc" , 718, ((const char*) (__PRETTY_FUNCTION__)), "m_start" " " "<" " " "m_writable", (long double) __n1, "<", (long double) __n2 , 'i'); } while (0); | |||
719 | ||||
720 | ensure_writable_room(); | |||
721 | ||||
722 | m_writable--; | |||
723 | ||||
724 | if (m_writable == m_cached_row_num) | |||
725 | m_cached_row_num = (row_t)-1; /* Invalidate cached row */ | |||
726 | ||||
727 | row = get_writable_index(m_writable); | |||
728 | thaw_row(m_writable, row, true, -1, nullptr); | |||
729 | } | |||
730 | ||||
731 | void | |||
732 | Ring::discard_one_row() | |||
733 | { | |||
734 | m_start++; | |||
735 | if (G_UNLIKELY(m_start == m_writable)(__builtin_expect (__extension__ ({ int _g_boolean_var_64; if (m_start == m_writable) _g_boolean_var_64 = 1; else _g_boolean_var_64 = 0; _g_boolean_var_64; }), 0))) { | |||
736 | reset_streams(m_writable); | |||
737 | } else if (m_start < m_writable) { | |||
738 | RowRecord record; | |||
739 | _bte_stream_advance_tail(m_row_stream, m_start * sizeof (record)); | |||
740 | if (G_LIKELY(read_row_record(&record, m_start))(__builtin_expect (__extension__ ({ int _g_boolean_var_65; if (read_row_record(&record, m_start)) _g_boolean_var_65 = 1 ; else _g_boolean_var_65 = 0; _g_boolean_var_65; }), 1))) { | |||
741 | _bte_stream_advance_tail(m_text_stream, record.text_start_offset); | |||
742 | _bte_stream_advance_tail(m_attr_stream, record.attr_start_offset); | |||
743 | } | |||
744 | } else { | |||
745 | m_writable = m_start; | |||
746 | } | |||
747 | } | |||
748 | ||||
749 | void | |||
750 | Ring::maybe_freeze_one_row() | |||
751 | { | |||
752 | /* See the comment about m_visible_rows + 1 at ensure_writable_room(). */ | |||
753 | if (G_LIKELY(m_mask >= m_visible_rows + 1 &&(__builtin_expect (__extension__ ({ int _g_boolean_var_66; if (m_mask >= m_visible_rows + 1 && m_writable + m_mask + 1 == m_end) _g_boolean_var_66 = 1; else _g_boolean_var_66 = 0; _g_boolean_var_66; }), 1)) | |||
754 | m_writable + m_mask + 1 == m_end)(__builtin_expect (__extension__ ({ int _g_boolean_var_66; if (m_mask >= m_visible_rows + 1 && m_writable + m_mask + 1 == m_end) _g_boolean_var_66 = 1; else _g_boolean_var_66 = 0; _g_boolean_var_66; }), 1))) | |||
755 | freeze_one_row(); | |||
756 | else | |||
757 | ensure_writable_room(); | |||
758 | } | |||
759 | ||||
760 | //FIXMEchpe maybe inline this one | |||
761 | void | |||
762 | Ring::maybe_discard_one_row() | |||
763 | { | |||
764 | if (length() == m_max) | |||
765 | discard_one_row(); | |||
766 | } | |||
767 | ||||
768 | void | |||
769 | Ring::ensure_writable_room() | |||
770 | { | |||
771 | row_t new_mask, old_mask, i, end; | |||
772 | BteRowData* old_array, *new_array;; | |||
773 | ||||
774 | /* Keep at least m_visible_rows + 1 rows in the ring. | |||
775 | * The BiDi spec requires that the just scrolled out row | |||
776 | * is still alterable (can be switched to hard line ending). | |||
777 | * It's nice anyway to make that hard wrapped upon a clear. */ | |||
778 | if (G_LIKELY(m_mask >= m_visible_rows + 1 &&(__builtin_expect (__extension__ ({ int _g_boolean_var_67; if (m_mask >= m_visible_rows + 1 && m_writable + m_mask + 1 > m_end) _g_boolean_var_67 = 1; else _g_boolean_var_67 = 0; _g_boolean_var_67; }), 1)) | |||
779 | m_writable + m_mask + 1 > m_end)(__builtin_expect (__extension__ ({ int _g_boolean_var_67; if (m_mask >= m_visible_rows + 1 && m_writable + m_mask + 1 > m_end) _g_boolean_var_67 = 1; else _g_boolean_var_67 = 0; _g_boolean_var_67; }), 1))) | |||
780 | return; | |||
781 | ||||
782 | old_mask = m_mask; | |||
783 | old_array = m_array; | |||
784 | ||||
785 | do { | |||
786 | m_mask = (m_mask << 1) + 1; | |||
787 | } while (m_mask < m_visible_rows + 1 || m_writable + m_mask + 1 <= m_end); | |||
788 | ||||
789 | _bte_debug_print(BTE_DEBUG_RING, "Enlarging writable array from %lu to %lu\n", old_mask, m_mask)do { } while(0); | |||
790 | ||||
791 | m_array = (BteRowData* ) g_malloc0(sizeof (m_array[0]) * (m_mask + 1)); | |||
792 | ||||
793 | new_mask = m_mask; | |||
794 | new_array = m_array; | |||
795 | ||||
796 | end = m_writable + old_mask + 1; | |||
797 | for (i = m_writable; i < end; i++) | |||
798 | new_array[i & new_mask] = old_array[i & old_mask]; | |||
799 | ||||
800 | g_free (old_array); | |||
801 | } | |||
802 | ||||
803 | void | |||
804 | Ring::ensure_writable(row_t position) | |||
805 | { | |||
806 | if (G_LIKELY(position >= m_writable)(__builtin_expect (__extension__ ({ int _g_boolean_var_68; if (position >= m_writable) _g_boolean_var_68 = 1; else _g_boolean_var_68 = 0; _g_boolean_var_68; }), 1))) | |||
807 | return; | |||
808 | ||||
809 | _bte_debug_print(BTE_DEBUG_RING, "Ensure writable %lu.\n", position)do { } while(0); | |||
810 | ||||
811 | //FIXMEchpe surely this can be optimised | |||
812 | while (position < m_writable) | |||
813 | thaw_one_row(); | |||
814 | } | |||
815 | ||||
816 | /** | |||
817 | * Ring::resize: | |||
818 | * @max_rows: new maximum numbers of rows in the ring | |||
819 | * | |||
820 | * Changes the number of lines the ring can contain. | |||
821 | */ | |||
822 | void | |||
823 | Ring::resize(row_t max_rows) | |||
824 | { | |||
825 | _bte_debug_print(BTE_DEBUG_RING, "Resizing to %lu.\n", max_rows)do { } while(0); | |||
826 | ||||
827 | validate()do { } while(0); | |||
828 | ||||
829 | /* Adjust the start of tail chunk now */ | |||
830 | if (length() > max_rows) { | |||
831 | m_start = m_end - max_rows; | |||
832 | if (m_start >= m_writable) { | |||
833 | reset_streams(m_writable); | |||
834 | m_writable = m_start; | |||
835 | } | |||
836 | } | |||
837 | ||||
838 | m_max = max_rows; | |||
839 | } | |||
840 | ||||
841 | void | |||
842 | Ring::shrink(row_t max_len) | |||
843 | { | |||
844 | if (length() <= max_len) | |||
845 | return; | |||
846 | ||||
847 | _bte_debug_print(BTE_DEBUG_RING, "Shrinking to %lu.\n", max_len)do { } while(0); | |||
848 | ||||
849 | validate()do { } while(0); | |||
850 | ||||
851 | if (m_writable - m_start <= max_len) | |||
852 | m_end = m_start + max_len; | |||
853 | else { | |||
854 | while (m_writable - m_start > max_len) { | |||
855 | ensure_writable(m_writable - 1); | |||
856 | m_end = m_writable; | |||
857 | } | |||
858 | } | |||
859 | ||||
860 | /* TODO May want to shrink down m_array */ | |||
861 | ||||
862 | validate()do { } while(0); | |||
863 | } | |||
864 | ||||
865 | /** | |||
866 | * Ring::insert: | |||
867 | * @position: an index | |||
868 | * | |||
869 | * Inserts a new, empty, row into @ring at the @position'th offset. | |||
870 | * The item at that position and any items after that are shifted down. | |||
871 | * | |||
872 | * Return: the newly added row. | |||
873 | */ | |||
874 | BteRowData* | |||
875 | Ring::insert(row_t position, guint8 bidi_flags) | |||
876 | { | |||
877 | row_t i; | |||
878 | BteRowData* row, tmp; | |||
879 | ||||
880 | _bte_debug_print(BTE_DEBUG_RING, "Inserting at position %lu.\n", position)do { } while(0); | |||
881 | validate()do { } while(0); | |||
882 | ||||
883 | maybe_discard_one_row(); | |||
884 | ensure_writable(position); | |||
885 | ensure_writable_room(); | |||
886 | ||||
887 | g_assert_cmpuint (position, >=, m_writable)do { guint64 __n1 = (position), __n2 = (m_writable); if (__n1 >= __n2) ; else g_assertion_message_cmpnum ("BTE", "../src/ring.cc" , 887, ((const char*) (__PRETTY_FUNCTION__)), "position" " " ">=" " " "m_writable", (long double) __n1, ">=", (long double) __n2, 'i'); } while (0); | |||
888 | g_assert_cmpuint (position, <=, m_end)do { guint64 __n1 = (position), __n2 = (m_end); if (__n1 <= __n2) ; else g_assertion_message_cmpnum ("BTE", "../src/ring.cc" , 888, ((const char*) (__PRETTY_FUNCTION__)), "position" " " "<=" " " "m_end", (long double) __n1, "<=", (long double) __n2 , 'i'); } while (0); | |||
889 | ||||
890 | //FIXMEchpe WTF use better data structures! | |||
891 | tmp = *get_writable_index(m_end); | |||
892 | for (i = m_end; i > position; i--) | |||
893 | *get_writable_index(i) = *get_writable_index(i - 1); | |||
894 | *get_writable_index(position) = tmp; | |||
895 | ||||
896 | row = get_writable_index(position); | |||
897 | _bte_row_data_clear (row); | |||
898 | row->attr.bidi_flags = bidi_flags; | |||
899 | m_end++; | |||
900 | ||||
901 | maybe_freeze_one_row(); | |||
902 | validate()do { } while(0); | |||
903 | return row; | |||
904 | } | |||
905 | ||||
906 | /** | |||
907 | * Ring::remove: | |||
908 | * @position: an index | |||
909 | * | |||
910 | * Removes the @position'th item from @ring. | |||
911 | */ | |||
912 | void | |||
913 | Ring::remove(row_t position) | |||
914 | { | |||
915 | row_t i; | |||
916 | BteRowData tmp; | |||
917 | ||||
918 | _bte_debug_print(BTE_DEBUG_RING, "Removing item at position %lu.\n", position)do { } while(0); | |||
919 | validate()do { } while(0); | |||
920 | ||||
921 | if (G_UNLIKELY(!contains(position))(__builtin_expect (__extension__ ({ int _g_boolean_var_69; if (!contains(position)) _g_boolean_var_69 = 1; else _g_boolean_var_69 = 0; _g_boolean_var_69; }), 0))) | |||
922 | return; | |||
923 | ||||
924 | ensure_writable(position); | |||
925 | ||||
926 | //FIXMEchpe WTF as above | |||
927 | tmp = *get_writable_index(position); | |||
928 | for (i = position; i < m_end - 1; i++) | |||
929 | *get_writable_index(i) = *get_writable_index(i + 1); | |||
930 | *get_writable_index(m_end - 1) = tmp; | |||
931 | ||||
932 | if (m_end > m_writable) | |||
933 | m_end--; | |||
934 | ||||
935 | validate()do { } while(0); | |||
936 | } | |||
937 | ||||
938 | ||||
939 | /** | |||
940 | * Ring::append: | |||
941 | * @data: the new item | |||
942 | * | |||
943 | * Appends a new item to the ring. | |||
944 | * | |||
945 | * Return: the newly added row. | |||
946 | */ | |||
947 | BteRowData* | |||
948 | Ring::append(guint8 bidi_flags) | |||
949 | { | |||
950 | return insert(next(), bidi_flags); | |||
951 | } | |||
952 | ||||
953 | ||||
954 | /** | |||
955 | * Ring::drop_scrollback: | |||
956 | * @position: drop contents up to this point, which must be in the writable region. | |||
957 | * | |||
958 | * Drop the scrollback (offscreen contents). | |||
959 | * | |||
960 | * TODOegmont: We wouldn't need the position argument after addressing 708213#c29. | |||
961 | */ | |||
962 | void | |||
963 | Ring::drop_scrollback(row_t position) | |||
964 | { | |||
965 | ensure_writable(position); | |||
966 | ||||
967 | m_start = m_writable = position; | |||
968 | reset_streams(position); | |||
969 | } | |||
970 | ||||
971 | /** | |||
972 | * Ring::set_visible_rows: | |||
973 | * @rows: the number of visible rows | |||
974 | * | |||
975 | * Set the number of visible rows. | |||
976 | * It's required to be set correctly for the alternate screen so that it | |||
977 | * never hits the streams. It's also required for clearing the scrollback. | |||
978 | */ | |||
979 | void | |||
980 | Ring::set_visible_rows(row_t rows) | |||
981 | { | |||
982 | m_visible_rows = rows; | |||
983 | } | |||
984 | ||||
985 | ||||
986 | /* Convert a (row,col) into a CellTextOffset. | |||
987 | * Requires the row to be frozen, or be outsize the range covered by the ring. | |||
988 | */ | |||
989 | bool | |||
990 | Ring::frozen_row_column_to_text_offset(row_t position, | |||
991 | column_t column, | |||
992 | CellTextOffset* offset) | |||
993 | { | |||
994 | RowRecord records[2]; | |||
995 | BteCell *cell; | |||
996 | GString *buffer = m_utf8_buffer; | |||
997 | BteRowData const* row; | |||
998 | unsigned int i, num_chars, off; | |||
999 | ||||
1000 | if (position >= m_end) { | |||
1001 | offset->text_offset = _bte_stream_head(m_text_stream) + position - m_end; | |||
1002 | offset->fragment_cells = 0; | |||
1003 | offset->eol_cells = column; | |||
1004 | return true; | |||
1005 | } | |||
1006 | ||||
1007 | if (G_UNLIKELY (position < m_start)(__builtin_expect (__extension__ ({ int _g_boolean_var_70; if (position < m_start) _g_boolean_var_70 = 1; else _g_boolean_var_70 = 0; _g_boolean_var_70; }), 0))) { | |||
1008 | /* This happens when the marker (saved cursor position) is | |||
1009 | scrolled off at the top of the scrollback buffer. */ | |||
1010 | position = m_start; | |||
1011 | column = 0; | |||
1012 | /* go on */ | |||
1013 | } | |||
1014 | ||||
1015 | g_assert_cmpuint(position, <, m_writable)do { guint64 __n1 = (position), __n2 = (m_writable); if (__n1 < __n2) ; else g_assertion_message_cmpnum ("BTE", "../src/ring.cc" , 1015, ((const char*) (__PRETTY_FUNCTION__)), "position" " " "<" " " "m_writable", (long double) __n1, "<", (long double ) __n2, 'i'); } while (0); | |||
1016 | if (!read_row_record(&records[0], position)) | |||
1017 | return false; | |||
1018 | if ((position + 1) * sizeof (records[0]) < _bte_stream_head(m_row_stream)) { | |||
1019 | if (!read_row_record(&records[1], position + 1)) | |||
1020 | return false; | |||
1021 | } else | |||
1022 | records[1].text_start_offset = _bte_stream_head(m_text_stream); | |||
1023 | ||||
1024 | g_string_set_size (buffer, records[1].text_start_offset - records[0].text_start_offset); | |||
1025 | if (!_bte_stream_read(m_text_stream, records[0].text_start_offset, buffer->str, buffer->len)) | |||
1026 | return false; | |||
1027 | ||||
1028 | if (G_LIKELY (buffer->len && buffer->str[buffer->len - 1] == '\n')(__builtin_expect (__extension__ ({ int _g_boolean_var_71; if (buffer->len && buffer->str[buffer->len - 1 ] == '\n') _g_boolean_var_71 = 1; else _g_boolean_var_71 = 0; _g_boolean_var_71; }), 1))) | |||
1029 | g_string_truncate (buffer, buffer->len - 1)g_string_truncate_inline (buffer, buffer->len - 1); | |||
1030 | ||||
1031 | row = index(position); | |||
1032 | ||||
1033 | /* row and buffer now contain the same text, in different representation */ | |||
1034 | ||||
1035 | /* count the number of characters up to the given column */ | |||
1036 | offset->fragment_cells = 0; | |||
1037 | offset->eol_cells = -1; | |||
1038 | num_chars = 0; | |||
1039 | for (i = 0, cell = row->cells; i < row->len && i < column; i++, cell++) { | |||
1040 | if (G_LIKELY (!cell->attr.fragment())(__builtin_expect (__extension__ ({ int _g_boolean_var_72; if (!cell->attr.fragment()) _g_boolean_var_72 = 1; else _g_boolean_var_72 = 0; _g_boolean_var_72; }), 1))) { | |||
1041 | if (G_UNLIKELY (i + cell->attr.columns() > column)(__builtin_expect (__extension__ ({ int _g_boolean_var_73; if (i + cell->attr.columns() > column) _g_boolean_var_73 = 1; else _g_boolean_var_73 = 0; _g_boolean_var_73; }), 0))) { | |||
1042 | offset->fragment_cells = column - i; | |||
1043 | break; | |||
1044 | } | |||
1045 | num_chars += _bte_unistr_strlen(cell->c); | |||
1046 | } | |||
1047 | } | |||
1048 | if (i >= row->len) { | |||
1049 | offset->eol_cells = column - i; | |||
1050 | } | |||
1051 | ||||
1052 | /* count the number of UTF-8 bytes for the given number of characters */ | |||
1053 | off = 0; | |||
1054 | while (num_chars > 0 && off < buffer->len) { | |||
1055 | off++; | |||
1056 | if ((buffer->str[off] & 0xC0) != 0x80) num_chars--; | |||
1057 | } | |||
1058 | offset->text_offset = records[0].text_start_offset + off; | |||
1059 | return true; | |||
1060 | } | |||
1061 | ||||
1062 | ||||
1063 | /* Given a row number and a CellTextOffset, compute the column within that row. | |||
1064 | It's the caller's responsibility to ensure that CellTextOffset really falls into that row. | |||
1065 | Requires the row to be frozen, or be outsize the range covered by the ring. | |||
1066 | */ | |||
1067 | bool | |||
1068 | Ring::frozen_row_text_offset_to_column(row_t position, | |||
1069 | CellTextOffset const* offset, | |||
1070 | column_t* column) | |||
1071 | { | |||
1072 | RowRecord records[2]; | |||
1073 | BteCell *cell; | |||
1074 | GString *buffer = m_utf8_buffer; | |||
1075 | BteRowData const* row; | |||
1076 | unsigned int i, off, num_chars, nc; | |||
1077 | ||||
1078 | if (position >= m_end) { | |||
1079 | *column = offset->eol_cells; | |||
1080 | return true; | |||
1081 | } | |||
1082 | ||||
1083 | if (G_UNLIKELY (position < m_start)(__builtin_expect (__extension__ ({ int _g_boolean_var_74; if (position < m_start) _g_boolean_var_74 = 1; else _g_boolean_var_74 = 0; _g_boolean_var_74; }), 0))) { | |||
1084 | /* This happens when the marker (saved cursor position) is | |||
1085 | scrolled off at the top of the scrollback buffer. */ | |||
1086 | *column = 0; | |||
1087 | return true; | |||
1088 | } | |||
1089 | ||||
1090 | g_assert_cmpuint(position, <, m_writable)do { guint64 __n1 = (position), __n2 = (m_writable); if (__n1 < __n2) ; else g_assertion_message_cmpnum ("BTE", "../src/ring.cc" , 1090, ((const char*) (__PRETTY_FUNCTION__)), "position" " " "<" " " "m_writable", (long double) __n1, "<", (long double ) __n2, 'i'); } while (0); | |||
1091 | if (!read_row_record(&records[0], position)) | |||
1092 | return false; | |||
1093 | if ((position + 1) * sizeof (records[0]) < _bte_stream_head(m_row_stream)) { | |||
1094 | if (!read_row_record(&records[1], position + 1)) | |||
1095 | return false; | |||
1096 | } else | |||
1097 | records[1].text_start_offset = _bte_stream_head (m_text_stream); | |||
1098 | ||||
1099 | g_string_set_size (buffer, records[1].text_start_offset - records[0].text_start_offset); | |||
1100 | if (!_bte_stream_read(m_text_stream, records[0].text_start_offset, buffer->str, buffer->len)) | |||
1101 | return false; | |||
1102 | ||||
1103 | if (G_LIKELY (buffer->len && buffer->str[buffer->len - 1] == '\n')(__builtin_expect (__extension__ ({ int _g_boolean_var_75; if (buffer->len && buffer->str[buffer->len - 1 ] == '\n') _g_boolean_var_75 = 1; else _g_boolean_var_75 = 0; _g_boolean_var_75; }), 1))) | |||
1104 | g_string_truncate (buffer, buffer->len - 1)g_string_truncate_inline (buffer, buffer->len - 1); | |||
1105 | ||||
1106 | /* Now that we've chopped off the likely trailing newline (which is only rarely missing, | |||
1107 | * if the ring ends in a soft wrapped line; see bug 181), the position we're about to | |||
1108 | * locate can be anywhere in the string, including just after its last character, | |||
1109 | * but not beyond that. */ | |||
1110 | g_assert_cmpuint(offset->text_offset, >=, records[0].text_start_offset)do { guint64 __n1 = (offset->text_offset), __n2 = (records [0].text_start_offset); if (__n1 >= __n2) ; else g_assertion_message_cmpnum ("BTE", "../src/ring.cc", 1110, ((const char*) (__PRETTY_FUNCTION__ )), "offset->text_offset" " " ">=" " " "records[0].text_start_offset" , (long double) __n1, ">=", (long double) __n2, 'i'); } while (0); | |||
1111 | g_assert_cmpuint(offset->text_offset, <=, records[0].text_start_offset + buffer->len)do { guint64 __n1 = (offset->text_offset), __n2 = (records [0].text_start_offset + buffer->len); if (__n1 <= __n2) ; else g_assertion_message_cmpnum ("BTE", "../src/ring.cc", 1111 , ((const char*) (__PRETTY_FUNCTION__)), "offset->text_offset" " " "<=" " " "records[0].text_start_offset + buffer->len" , (long double) __n1, "<=", (long double) __n2, 'i'); } while (0); | |||
1112 | ||||
1113 | row = index(position); | |||
1114 | ||||
1115 | /* row and buffer now contain the same text, in different representation */ | |||
1116 | ||||
1117 | /* count the number of characters for the given UTF-8 text offset */ | |||
1118 | off = offset->text_offset - records[0].text_start_offset; | |||
1119 | num_chars = 0; | |||
1120 | for (i = 0; i < off; i++) { | |||
1121 | if ((buffer->str[i] & 0xC0) != 0x80) num_chars++; | |||
1122 | } | |||
1123 | ||||
1124 | /* count the number of columns for the given number of characters */ | |||
1125 | for (i = 0, cell = row->cells; i < row->len; i++, cell++) { | |||
1126 | if (G_LIKELY (!cell->attr.fragment())(__builtin_expect (__extension__ ({ int _g_boolean_var_76; if (!cell->attr.fragment()) _g_boolean_var_76 = 1; else _g_boolean_var_76 = 0; _g_boolean_var_76; }), 1))) { | |||
1127 | if (num_chars == 0) break; | |||
1128 | nc = _bte_unistr_strlen(cell->c); | |||
1129 | if (nc > num_chars) break; | |||
1130 | num_chars -= nc; | |||
1131 | } | |||
1132 | } | |||
1133 | ||||
1134 | /* always add fragment_cells, but add eol_cells only if we're at eol */ | |||
1135 | i += offset->fragment_cells; | |||
1136 | if (G_UNLIKELY (offset->eol_cells >= 0 && i == row->len)(__builtin_expect (__extension__ ({ int _g_boolean_var_77; if (offset->eol_cells >= 0 && i == row->len) _g_boolean_var_77 = 1; else _g_boolean_var_77 = 0; _g_boolean_var_77; }), 0))) | |||
1137 | i += offset->eol_cells; | |||
1138 | *column = i; | |||
1139 | return true; | |||
1140 | } | |||
1141 | ||||
1142 | ||||
1143 | /** | |||
1144 | * Ring::rewrap: | |||
1145 | * @columns: new number of columns | |||
1146 | * @markers: 0-terminated array of #BteVisualPosition | |||
1147 | * | |||
1148 | * Reflow the @ring to match the new number of @columns. | |||
1149 | * For all @markers, find the cell at that position and update them to | |||
1150 | * reflect the cell's new position. | |||
1151 | */ | |||
1152 | /* See ../doc/rewrap.txt for design and implementation details. */ | |||
1153 | void | |||
1154 | Ring::rewrap(column_t columns, | |||
1155 | BteVisualPosition** markers) | |||
1156 | { | |||
1157 | row_t old_row_index, new_row_index; | |||
1158 | int i; | |||
1159 | int num_markers = 0; | |||
1160 | CellTextOffset *marker_text_offsets; | |||
1161 | BteVisualPosition *new_markers; | |||
1162 | RowRecord old_record; | |||
1163 | CellAttrChange attr_change; | |||
1164 | BteStream *new_row_stream; | |||
1165 | gsize paragraph_start_text_offset; | |||
1166 | gsize paragraph_end_text_offset; | |||
1167 | gsize paragraph_len; /* excluding trailing '\n' */ | |||
1168 | gsize attr_offset; | |||
1169 | gsize old_ring_end; | |||
1170 | ||||
1171 | if (G_UNLIKELY(length() == 0)(__builtin_expect (__extension__ ({ int _g_boolean_var_78; if (length() == 0) _g_boolean_var_78 = 1; else _g_boolean_var_78 = 0; _g_boolean_var_78; }), 0))) | |||
1172 | return; | |||
1173 | _bte_debug_print(BTE_DEBUG_RING, "Ring before rewrapping:\n")do { } while(0); | |||
1174 | validate()do { } while(0); | |||
1175 | new_row_stream = _bte_file_stream_new(); | |||
1176 | ||||
1177 | /* Freeze everything, because rewrapping is really complicated and we don't want to | |||
1178 | duplicate the code for frozen and thawed rows. */ | |||
1179 | while (m_writable < m_end) | |||
1180 | freeze_one_row(); | |||
1181 | ||||
1182 | /* For markers given as (row,col) pairs find their offsets in the text stream. | |||
1183 | This code requires that the rows are already frozen. */ | |||
1184 | while (markers[num_markers] != nullptr) | |||
1185 | num_markers++; | |||
1186 | marker_text_offsets = (CellTextOffset *) g_malloc(num_markers * sizeof (marker_text_offsets[0])); | |||
1187 | new_markers = (BteVisualPosition *) g_malloc(num_markers * sizeof (new_markers[0])); | |||
1188 | for (i = 0; i < num_markers; i++) { | |||
1189 | /* Convert visual column into byte offset */ | |||
1190 | if (!frozen_row_column_to_text_offset(markers[i]->row, markers[i]->col, &marker_text_offsets[i])) | |||
1191 | goto err; | |||
1192 | new_markers[i].row = new_markers[i].col = -1; | |||
1193 | _bte_debug_print(BTE_DEBUG_RING,do { } while(0) | |||
1194 | "Marker #%d old coords: row %ld col %ld -> text_offset %" G_GSIZE_FORMAT " fragment_cells %d eol_cells %d\n",do { } while(0) | |||
1195 | i, markers[i]->row, markers[i]->col, marker_text_offsets[i].text_offset,do { } while(0) | |||
1196 | marker_text_offsets[i].fragment_cells, marker_text_offsets[i].eol_cells)do { } while(0); | |||
1197 | } | |||
1198 | ||||
1199 | /* Prepare for rewrapping */ | |||
1200 | if (!read_row_record(&old_record, m_start)) | |||
1201 | goto err; | |||
1202 | paragraph_start_text_offset = old_record.text_start_offset; | |||
1203 | paragraph_end_text_offset = _bte_stream_head(m_text_stream); /* initialized to silence gcc */ | |||
1204 | new_row_index = 0; | |||
1205 | ||||
1206 | attr_offset = old_record.attr_start_offset; | |||
1207 | if (!_bte_stream_read(m_attr_stream, attr_offset, (char *) &attr_change, sizeof (attr_change))) { | |||
1208 | _attrcpy(&attr_change.attr, &m_last_attr); | |||
1209 | attr_change.attr.hyperlink_length = hyperlink_get(m_last_attr.hyperlink_idx)->len; | |||
1210 | attr_change.text_end_offset = _bte_stream_head(m_text_stream); | |||
1211 | } | |||
1212 | ||||
1213 | old_row_index = m_start + 1; | |||
1214 | while (paragraph_start_text_offset < _bte_stream_head(m_text_stream)) { | |||
1215 | /* Find the boundaries of the next paragraph */ | |||
1216 | gboolean prev_record_was_soft_wrapped = FALSE(0); | |||
1217 | gboolean paragraph_is_ascii = TRUE(!(0)); | |||
1218 | guint8 paragraph_bidi_flags = old_record.bidi_flags; | |||
1219 | gsize text_offset = paragraph_start_text_offset; | |||
1220 | RowRecord new_record; | |||
1221 | column_t col = 0; | |||
1222 | ||||
1223 | _bte_debug_print(BTE_DEBUG_RING,do { } while(0) | |||
1224 | " Old paragraph: row %lu (text_offset %" G_GSIZE_FORMAT ") up to (exclusive) ", /* no '\n' */do { } while(0) | |||
1225 | old_row_index - 1,do { } while(0) | |||
1226 | paragraph_start_text_offset)do { } while(0); | |||
1227 | while (old_row_index <= m_end) { | |||
1228 | prev_record_was_soft_wrapped = old_record.soft_wrapped; | |||
1229 | paragraph_is_ascii = paragraph_is_ascii && old_record.is_ascii; | |||
1230 | if (G_LIKELY (old_row_index < m_end)(__builtin_expect (__extension__ ({ int _g_boolean_var_79; if (old_row_index < m_end) _g_boolean_var_79 = 1; else _g_boolean_var_79 = 0; _g_boolean_var_79; }), 1))) { | |||
1231 | if (!read_row_record(&old_record, old_row_index)) | |||
1232 | goto err; | |||
1233 | paragraph_end_text_offset = old_record.text_start_offset; | |||
1234 | } else { | |||
1235 | paragraph_end_text_offset = _bte_stream_head (m_text_stream); | |||
1236 | } | |||
1237 | old_row_index++; | |||
1238 | if (!prev_record_was_soft_wrapped) | |||
1239 | break; | |||
1240 | } | |||
1241 | ||||
1242 | paragraph_len = paragraph_end_text_offset - paragraph_start_text_offset; | |||
1243 | if (!prev_record_was_soft_wrapped) /* The last paragraph can be soft wrapped! */ | |||
1244 | paragraph_len--; /* Strip trailing '\n' */ | |||
1245 | _bte_debug_print(BTE_DEBUG_RING,do { } while(0) | |||
1246 | "row %lu (text_offset %" G_GSIZE_FORMAT ")%s len %" G_GSIZE_FORMAT " is_ascii %d\n",do { } while(0) | |||
1247 | old_row_index - 1,do { } while(0) | |||
1248 | paragraph_end_text_offset,do { } while(0) | |||
1249 | prev_record_was_soft_wrapped ? " soft_wrapped" : "",do { } while(0) | |||
1250 | paragraph_len, paragraph_is_ascii)do { } while(0); | |||
1251 | ||||
1252 | /* Wrap the paragraph */ | |||
1253 | if (attr_change.text_end_offset <= text_offset) { | |||
1254 | /* Attr change at paragraph boundary, advance to next attr. */ | |||
1255 | attr_offset += sizeof (attr_change) + attr_change.attr.hyperlink_length + 2; | |||
1256 | if (!_bte_stream_read(m_attr_stream, attr_offset, (char *) &attr_change, sizeof (attr_change))) { | |||
1257 | _attrcpy(&attr_change.attr, &m_last_attr); | |||
1258 | attr_change.attr.hyperlink_length = hyperlink_get(m_last_attr.hyperlink_idx)->len; | |||
1259 | attr_change.text_end_offset = _bte_stream_head(m_text_stream); | |||
1260 | } | |||
1261 | } | |||
1262 | memset(&new_record, 0, sizeof (new_record)); | |||
1263 | new_record.text_start_offset = text_offset; | |||
1264 | new_record.attr_start_offset = attr_offset; | |||
1265 | new_record.is_ascii = paragraph_is_ascii; | |||
1266 | new_record.bidi_flags = paragraph_bidi_flags; | |||
1267 | ||||
1268 | while (paragraph_len > 0) { | |||
1269 | /* Wrap one continuous run of identical attributes within the paragraph. */ | |||
1270 | gsize runlength; /* number of bytes we process in one run: identical attributes, within paragraph */ | |||
1271 | if (attr_change.text_end_offset <= text_offset) { | |||
1272 | /* Attr change at line boundary, advance to next attr. */ | |||
1273 | attr_offset += sizeof (attr_change) + attr_change.attr.hyperlink_length + 2; | |||
1274 | if (!_bte_stream_read(m_attr_stream, attr_offset, (char *) &attr_change, sizeof (attr_change))) { | |||
1275 | _attrcpy(&attr_change.attr, &m_last_attr); | |||
1276 | attr_change.attr.hyperlink_length = hyperlink_get(m_last_attr.hyperlink_idx)->len; | |||
1277 | attr_change.text_end_offset = _bte_stream_head(m_text_stream); | |||
1278 | } | |||
1279 | } | |||
1280 | runlength = MIN(paragraph_len, attr_change.text_end_offset - text_offset)(((paragraph_len) < (attr_change.text_end_offset - text_offset )) ? (paragraph_len) : (attr_change.text_end_offset - text_offset )); | |||
1281 | ||||
1282 | if (G_UNLIKELY (attr_change.attr.columns() == 0)(__builtin_expect (__extension__ ({ int _g_boolean_var_80; if (attr_change.attr.columns() == 0) _g_boolean_var_80 = 1; else _g_boolean_var_80 = 0; _g_boolean_var_80; }), 0))) { | |||
1283 | /* Combining characters all fit in the current row */ | |||
1284 | text_offset += runlength; | |||
1285 | paragraph_len -= runlength; | |||
1286 | } else { | |||
1287 | while (runlength) { | |||
1288 | if (col >= columns - attr_change.attr.columns() + 1) { | |||
1289 | /* Wrap now, write the soft wrapped row's record */ | |||
1290 | new_record.soft_wrapped = 1; | |||
1291 | _bte_stream_append(new_row_stream, (char const* ) &new_record, sizeof (new_record)); | |||
1292 | _bte_debug_print(BTE_DEBUG_RING,do { } while(0) | |||
1293 | " New row %ld text_offset %" G_GSIZE_FORMAT " attr_offset %" G_GSIZE_FORMAT " soft_wrapped\n",do { } while(0) | |||
1294 | new_row_index,do { } while(0) | |||
1295 | new_record.text_start_offset, new_record.attr_start_offset)do { } while(0); | |||
1296 | for (i = 0; i < num_markers; i++) { | |||
1297 | if (G_UNLIKELY (marker_text_offsets[i].text_offset >= new_record.text_start_offset &&(__builtin_expect (__extension__ ({ int _g_boolean_var_81; if (marker_text_offsets[i].text_offset >= new_record.text_start_offset && marker_text_offsets[i].text_offset < text_offset ) _g_boolean_var_81 = 1; else _g_boolean_var_81 = 0; _g_boolean_var_81 ; }), 0)) | |||
1298 | marker_text_offsets[i].text_offset < text_offset)(__builtin_expect (__extension__ ({ int _g_boolean_var_81; if (marker_text_offsets[i].text_offset >= new_record.text_start_offset && marker_text_offsets[i].text_offset < text_offset ) _g_boolean_var_81 = 1; else _g_boolean_var_81 = 0; _g_boolean_var_81 ; }), 0))) { | |||
1299 | new_markers[i].row = new_row_index; | |||
1300 | _bte_debug_print(BTE_DEBUG_RING,do { } while(0) | |||
1301 | " Marker #%d will be here in row %lu\n", i, new_row_index)do { } while(0); | |||
1302 | } | |||
1303 | } | |||
1304 | new_row_index++; | |||
1305 | new_record.text_start_offset = text_offset; | |||
1306 | new_record.attr_start_offset = attr_offset; | |||
1307 | col = 0; | |||
1308 | } | |||
1309 | if (paragraph_is_ascii) { | |||
1310 | /* Shortcut for quickly wrapping ASCII (excluding TAB) text. | |||
1311 | Don't read text_stream, and advance by a whole row of characters. */ | |||
1312 | int len = MIN(runlength, (gsize) (columns - col))(((runlength) < ((gsize) (columns - col))) ? (runlength) : ((gsize) (columns - col))); | |||
1313 | col += len; | |||
1314 | text_offset += len; | |||
1315 | paragraph_len -= len; | |||
1316 | runlength -= len; | |||
1317 | } else { | |||
1318 | /* Process one character only. */ | |||
1319 | char textbuf[6]; /* fits at least one UTF-8 character */ | |||
1320 | int textbuf_len; | |||
1321 | col += attr_change.attr.columns(); | |||
1322 | /* Find beginning of next UTF-8 character */ | |||
1323 | text_offset++; paragraph_len--; runlength--; | |||
1324 | textbuf_len = MIN(runlength, sizeof (textbuf))(((runlength) < (sizeof (textbuf))) ? (runlength) : (sizeof (textbuf))); | |||
1325 | if (!_bte_stream_read(m_text_stream, text_offset, textbuf, textbuf_len)) | |||
1326 | goto err; | |||
1327 | for (i = 0; i < textbuf_len && (textbuf[i] & 0xC0) == 0x80; i++) { | |||
1328 | text_offset++; paragraph_len--; runlength--; | |||
1329 | } | |||
1330 | } | |||
1331 | } | |||
1332 | } | |||
1333 | } | |||
1334 | ||||
1335 | /* Write the record of the paragraph's last row. */ | |||
1336 | /* Hard wrapped, except maybe at the end of the very last paragraph */ | |||
1337 | new_record.soft_wrapped = prev_record_was_soft_wrapped; | |||
1338 | _bte_stream_append(new_row_stream, (char const* ) &new_record, sizeof (new_record)); | |||
1339 | _bte_debug_print(BTE_DEBUG_RING,do { } while(0) | |||
1340 | " New row %ld text_offset %" G_GSIZE_FORMAT " attr_offset %" G_GSIZE_FORMAT "\n",do { } while(0) | |||
1341 | new_row_index,do { } while(0) | |||
1342 | new_record.text_start_offset, new_record.attr_start_offset)do { } while(0); | |||
1343 | for (i = 0; i < num_markers; i++) { | |||
1344 | if (G_UNLIKELY (marker_text_offsets[i].text_offset >= new_record.text_start_offset &&(__builtin_expect (__extension__ ({ int _g_boolean_var_82; if (marker_text_offsets[i].text_offset >= new_record.text_start_offset && marker_text_offsets[i].text_offset < paragraph_end_text_offset ) _g_boolean_var_82 = 1; else _g_boolean_var_82 = 0; _g_boolean_var_82 ; }), 0)) | |||
1345 | marker_text_offsets[i].text_offset < paragraph_end_text_offset)(__builtin_expect (__extension__ ({ int _g_boolean_var_82; if (marker_text_offsets[i].text_offset >= new_record.text_start_offset && marker_text_offsets[i].text_offset < paragraph_end_text_offset ) _g_boolean_var_82 = 1; else _g_boolean_var_82 = 0; _g_boolean_var_82 ; }), 0))) { | |||
1346 | new_markers[i].row = new_row_index; | |||
1347 | _bte_debug_print(BTE_DEBUG_RING,do { } while(0) | |||
1348 | " Marker #%d will be here in row %lu\n", i, new_row_index)do { } while(0); | |||
1349 | } | |||
1350 | } | |||
1351 | new_row_index++; | |||
1352 | paragraph_start_text_offset = paragraph_end_text_offset; | |||
1353 | } | |||
1354 | ||||
1355 | /* Update the ring. */ | |||
1356 | old_ring_end = m_end; | |||
1357 | g_object_unref(m_row_stream); | |||
1358 | m_row_stream = new_row_stream; | |||
1359 | m_writable = m_end = new_row_index; | |||
1360 | m_start = 0; | |||
1361 | if (m_end > m_max) | |||
1362 | m_start = m_end - m_max; | |||
1363 | m_cached_row_num = (row_t) -1; | |||
1364 | ||||
1365 | /* Find the markers. This requires that the ring is already updated. */ | |||
1366 | for (i = 0; i < num_markers; i++) { | |||
1367 | /* Compute the row for markers beyond the ring */ | |||
1368 | if (new_markers[i].row == -1) | |||
1369 | new_markers[i].row = markers[i]->row - old_ring_end + m_end; | |||
1370 | /* Convert byte offset into visual column */ | |||
1371 | if (!frozen_row_text_offset_to_column(new_markers[i].row, &marker_text_offsets[i], &new_markers[i].col)) | |||
1372 | goto err; | |||
1373 | _bte_debug_print(BTE_DEBUG_RING,do { } while(0) | |||
1374 | "Marker #%d new coords: text_offset %" G_GSIZE_FORMAT " fragment_cells %d eol_cells %d -> row %ld col %ld\n",do { } while(0) | |||
1375 | i, marker_text_offsets[i].text_offset, marker_text_offsets[i].fragment_cells,do { } while(0) | |||
1376 | marker_text_offsets[i].eol_cells, new_markers[i].row, new_markers[i].col)do { } while(0); | |||
1377 | markers[i]->row = new_markers[i].row; | |||
1378 | markers[i]->col = new_markers[i].col; | |||
1379 | } | |||
1380 | g_free(marker_text_offsets); | |||
1381 | g_free(new_markers); | |||
1382 | ||||
1383 | _bte_debug_print(BTE_DEBUG_RING, "Ring after rewrapping:\n")do { } while(0); | |||
1384 | validate()do { } while(0); | |||
1385 | return; | |||
1386 | ||||
1387 | err: | |||
1388 | #ifdef BTE_DEBUG | |||
1389 | _bte_debug_print(BTE_DEBUG_RING,do { } while(0) | |||
1390 | "Error while rewrapping\n")do { } while(0); | |||
1391 | g_assert_not_reached()do { g_assertion_message_expr ("BTE", "../src/ring.cc", 1391, ((const char*) (__PRETTY_FUNCTION__)), __null); } while (0); | |||
1392 | #endif | |||
1393 | g_object_unref(new_row_stream); | |||
1394 | g_free(marker_text_offsets); | |||
1395 | g_free(new_markers); | |||
1396 | } | |||
1397 | ||||
1398 | ||||
1399 | bool | |||
1400 | Ring::write_row(GOutputStream* stream, | |||
1401 | BteRowData* row, | |||
1402 | BteWriteFlags flags, | |||
1403 | GCancellable* cancellable, | |||
1404 | GError** error) | |||
1405 | { | |||
1406 | BteCell *cell; | |||
1407 | GString *buffer = m_utf8_buffer; | |||
1408 | int i; | |||
1409 | gsize bytes_written; | |||
1410 | ||||
1411 | /* Simple version of the loop in freeze_row(). | |||
1412 | * TODO Should unify one day */ | |||
1413 | g_string_set_size (buffer, 0); | |||
1414 | for (i = 0, cell = row->cells; i < row->len; i++, cell++) { | |||
1415 | if (G_LIKELY (!cell->attr.fragment())(__builtin_expect (__extension__ ({ int _g_boolean_var_83; if (!cell->attr.fragment()) _g_boolean_var_83 = 1; else _g_boolean_var_83 = 0; _g_boolean_var_83; }), 1))) | |||
1416 | _bte_unistr_append_to_string (cell->c, buffer); | |||
1417 | } | |||
1418 | if (!row->attr.soft_wrapped) | |||
1419 | g_string_append_c (buffer, '\n')g_string_append_c_inline (buffer, '\n'); | |||
1420 | ||||
1421 | return g_output_stream_write_all (stream, buffer->str, buffer->len, &bytes_written, cancellable, error); | |||
1422 | } | |||
1423 | ||||
1424 | /** | |||
1425 | * Ring::write_contents: | |||
1426 | * @stream: a #GOutputStream to write to | |||
1427 | * @flags: a set of #BteWriteFlags | |||
1428 | * @cancellable: optional #GCancellable object, %nullptr to ignore | |||
1429 | * @error: a #GError location to store the error occuring, or %nullptr to ignore | |||
1430 | * | |||
1431 | * Write entire ring contents to @stream according to @flags. | |||
1432 | * | |||
1433 | * Return: %TRUE on success, %FALSE if there was an error | |||
1434 | */ | |||
1435 | bool | |||
1436 | Ring::write_contents(GOutputStream* stream, | |||
1437 | BteWriteFlags flags, | |||
1438 | GCancellable* cancellable, | |||
1439 | GError** error) | |||
1440 | { | |||
1441 | row_t i; | |||
1442 | ||||
1443 | _bte_debug_print(BTE_DEBUG_RING, "Writing contents to GOutputStream.\n")do { } while(0); | |||
1444 | ||||
1445 | if (m_start < m_writable) | |||
1446 | { | |||
1447 | RowRecord record; | |||
1448 | ||||
1449 | if (read_row_record(&record, m_start)) | |||
1450 | { | |||
1451 | gsize start_offset = record.text_start_offset; | |||
1452 | gsize end_offset = _bte_stream_head(m_text_stream); | |||
1453 | char buf[4096]; | |||
1454 | while (start_offset < end_offset) | |||
1455 | { | |||
1456 | gsize bytes_written, len; | |||
1457 | ||||
1458 | len = MIN (G_N_ELEMENTS (buf), end_offset - start_offset)((((sizeof (buf) / sizeof ((buf)[0]))) < (end_offset - start_offset )) ? ((sizeof (buf) / sizeof ((buf)[0]))) : (end_offset - start_offset )); | |||
1459 | ||||
1460 | if (!_bte_stream_read (m_text_stream, start_offset, | |||
1461 | buf, len)) | |||
1462 | return false; | |||
1463 | ||||
1464 | if (!g_output_stream_write_all (stream, buf, len, | |||
1465 | &bytes_written, cancellable, | |||
1466 | error)) | |||
1467 | return false; | |||
1468 | ||||
1469 | start_offset += len; | |||
1470 | } | |||
1471 | } | |||
1472 | else | |||
1473 | //FIXMEchpe g_set_error!! | |||
1474 | return false; | |||
1475 | } | |||
1476 | ||||
1477 | for (i = m_writable; i < m_end; i++) { | |||
1478 | if (!write_row(stream, | |||
1479 | get_writable_index(i), | |||
1480 | flags, cancellable, error)) | |||
1481 | return false; | |||
1482 | } | |||
1483 | ||||
1484 | return true; | |||
1485 | } |