| 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_cmpint ("BTE", "../src/ring.cc" , 53, ((const char*) (__PRETTY_FUNCTION__)), "m_start" " " "<=" " " "m_writable", __n1, "<=", __n2, 'u'); } 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_cmpint ("BTE", "../src/ring.cc" , 54, ((const char*) (__PRETTY_FUNCTION__)), "m_writable" " " "<=" " " "m_end", __n1, "<=", __n2, 'u'); } 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_cmpint ("BTE", "../src/ring.cc" , 56, ((const char*) (__PRETTY_FUNCTION__)), "m_end - m_start" " " "<=" " " "m_max", __n1, "<=", __n2, 'u'); } 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_cmpint ("BTE", "../src/ring.cc" , 57, ((const char*) (__PRETTY_FUNCTION__)), "m_end - m_writable" " " "<=" " " "m_mask", __n1, "<=", __n2, 'u'); } 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)(__builtin_constant_p ((!(0))) ? (((!(0))) ? (g_string_free) ( (m_utf8_buffer), ((!(0)))) : g_string_free_and_steal (m_utf8_buffer )) : (g_string_free) ((m_utf8_buffer), ((!(0))))); | |||
| 106 | ||||
| 107 | for (size_t i = 0; i < m_hyperlinks->len; i++) | |||
| 108 | g_string_free (hyperlink_get(i), TRUE)(__builtin_constant_p ((!(0))) ? (((!(0))) ? (g_string_free) ( (hyperlink_get(i)), ((!(0)))) : g_string_free_and_steal (hyperlink_get (i))) : (g_string_free) ((hyperlink_get(i)), ((!(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_cmpint ("BTE", "../src/ring.cc", 243, ((const char*) (__PRETTY_FUNCTION__ )), "m_hyperlink_highest_used_idx + 1" " " "==" " " "m_hyperlinks->len" , __n1, "==", __n2, 'u'); } 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_cmpint ("BTE", "../src/ring.cc", 261, ((const char*) (__PRETTY_FUNCTION__ )), "m_hyperlink_highest_used_idx + 1" " " "==" " " "m_hyperlinks->len" , __n1, "==", __n2, 'u'); } 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 = 0; if (m_has_streams) _g_boolean_var_48 = 1; _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 = 0 ; if (!attr.fragment()) _g_boolean_var_49 = 1; _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 = 0 ; if (hyperlink->len != 0) _g_boolean_var_50 = 1; _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 = 0 ; if (hyperlink->len != 0) _g_boolean_var_51 = 1; _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 = 0; if (m_has_streams) _g_boolean_var_52 = 1; _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 = 0 ; if (buffer->len && buffer->str[buffer->len - 1] == '\n') _g_boolean_var_53 = 1; _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_cmpint ("BTE", "../src/ring.cc", 452, ((const char*) (__PRETTY_FUNCTION__ )), "attr_change.attr.hyperlink_length" " " "<=" " " "BTE_HYPERLINK_TOTAL_LENGTH_MAX" , __n1, "<=", __n2, 'u'); } 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 = 0 ; if (attr_change.attr.hyperlink_length) _g_boolean_var_54 = 1 ; _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)((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 = 0 ; if (cell.attr.columns() == 0) _g_boolean_var_55 = 1; _g_boolean_var_55 ; }), 0))) { | |||
| 491 | if (G_LIKELY (row->len)(__builtin_expect (__extension__ ({ int _g_boolean_var_56 = 0 ; if (row->len) _g_boolean_var_56 = 1; _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_cmpint ("BTE" , "../src/ring.cc", 532, ((const char*) (__PRETTY_FUNCTION__) ), "hyperlink_length" " " "<=" " " "BTE_HYPERLINK_TOTAL_LENGTH_MAX" , __n1, "<=", __n2, 'u'); } 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_cmpint ("BTE" , "../src/ring.cc", 550, ((const char*) (__PRETTY_FUNCTION__) ), "hyperlink_length" " " "<=" " " "BTE_HYPERLINK_TOTAL_LENGTH_MAX" , __n1, "<=", __n2, 'u'); } 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 = 0 ; if (position >= m_writable) _g_boolean_var_57 = 1; _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 = 0 ; if (position < m_start || position >= m_end) _g_boolean_var_58 = 1; _g_boolean_var_58; }), 0))) | |||
| 619 | return false; | |||
| 620 | ||||
| 621 | if (G_LIKELY (position >= m_writable)(__builtin_expect (__extension__ ({ int _g_boolean_var_59 = 0 ; if (position >= m_writable) _g_boolean_var_59 = 1; _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 = 0 ; if (!read_row_record(&record, position)) _g_boolean_var_60 = 1; _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 = 0 ; if (!contains(position) || col < 0) _g_boolean_var_61 = 1 ; _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 = 0 ; if (position >= m_writable) _g_boolean_var_62 = 1; _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 = 0 ; if (m_writable == m_start) _g_boolean_var_63 = 1; _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_cmpint ("BTE", "../src/ring.cc" , 718, ((const char*) (__PRETTY_FUNCTION__)), "m_start" " " "<" " " "m_writable", __n1, "<", __n2, 'u'); } 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 = 0 ; if (m_start == m_writable) _g_boolean_var_64 = 1; _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 = 0 ; if (read_row_record(&record, m_start)) _g_boolean_var_65 = 1; _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 = 0 ; if (m_mask >= m_visible_rows + 1 && m_writable + m_mask + 1 == m_end) _g_boolean_var_66 = 1; _g_boolean_var_66 ; }), 1)) | |||
| 754 | m_writable + m_mask + 1 == m_end)(__builtin_expect (__extension__ ({ int _g_boolean_var_66 = 0 ; if (m_mask >= m_visible_rows + 1 && m_writable + m_mask + 1 == m_end) _g_boolean_var_66 = 1; _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 = 0 ; if (m_mask >= m_visible_rows + 1 && m_writable + m_mask + 1 > m_end) _g_boolean_var_67 = 1; _g_boolean_var_67 ; }), 1)) | |||
| 779 | m_writable + m_mask + 1 > m_end)(__builtin_expect (__extension__ ({ int _g_boolean_var_67 = 0 ; if (m_mask >= m_visible_rows + 1 && m_writable + m_mask + 1 > m_end) _g_boolean_var_67 = 1; _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 = 0 ; if (position >= m_writable) _g_boolean_var_68 = 1; _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_cmpint ("BTE", "../src/ring.cc" , 887, ((const char*) (__PRETTY_FUNCTION__)), "position" " " ">=" " " "m_writable", __n1, ">=", __n2, 'u'); } while (0); | |||
| 888 | g_assert_cmpuint (position, <=, m_end)do { guint64 __n1 = (position), __n2 = (m_end); if (__n1 <= __n2) ; else g_assertion_message_cmpint ("BTE", "../src/ring.cc" , 888, ((const char*) (__PRETTY_FUNCTION__)), "position" " " "<=" " " "m_end", __n1, "<=", __n2, 'u'); } 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 = 0 ; if (!contains(position)) _g_boolean_var_69 = 1; _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 = 0 ; if (position < m_start) _g_boolean_var_70 = 1; _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_cmpint ("BTE", "../src/ring.cc" , 1015, ((const char*) (__PRETTY_FUNCTION__)), "position" " " "<" " " "m_writable", __n1, "<", __n2, 'u'); } 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 = 0 ; if (buffer->len && buffer->str[buffer->len - 1] == '\n') _g_boolean_var_71 = 1; _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 = 0 ; if (!cell->attr.fragment()) _g_boolean_var_72 = 1; _g_boolean_var_72 ; }), 1))) { | |||
| 1041 | if (G_UNLIKELY (i + cell->attr.columns() > column)(__builtin_expect (__extension__ ({ int _g_boolean_var_73 = 0 ; if (i + cell->attr.columns() > column) _g_boolean_var_73 = 1; _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 = 0 ; if (position < m_start) _g_boolean_var_74 = 1; _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_cmpint ("BTE", "../src/ring.cc" , 1090, ((const char*) (__PRETTY_FUNCTION__)), "position" " " "<" " " "m_writable", __n1, "<", __n2, 'u'); } 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 = 0 ; if (buffer->len && buffer->str[buffer->len - 1] == '\n') _g_boolean_var_75 = 1; _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_cmpint ("BTE", "../src/ring.cc", 1110, ((const char*) (__PRETTY_FUNCTION__ )), "offset->text_offset" " " ">=" " " "records[0].text_start_offset" , __n1, ">=", __n2, 'u'); } 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_cmpint ("BTE", "../src/ring.cc", 1111 , ((const char*) (__PRETTY_FUNCTION__)), "offset->text_offset" " " "<=" " " "records[0].text_start_offset + buffer->len" , __n1, "<=", __n2, 'u'); } 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 = 0 ; if (!cell->attr.fragment()) _g_boolean_var_76 = 1; _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 = 0 ; if (offset->eol_cells >= 0 && i == row->len ) _g_boolean_var_77 = 1; _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 = 0 ; if (length() == 0) _g_boolean_var_78 = 1; _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 = 0 ; if (old_row_index < m_end) _g_boolean_var_79 = 1; _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 = 0 ; if (attr_change.attr.columns() == 0) _g_boolean_var_80 = 1; _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 = 0 ; 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; _g_boolean_var_81; }), 0)) | |||
| 1298 | marker_text_offsets[i].text_offset < text_offset)(__builtin_expect (__extension__ ({ int _g_boolean_var_81 = 0 ; 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; _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 = 0 ; 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; _g_boolean_var_82; }), 0)) | |||
| 1345 | marker_text_offsets[i].text_offset < paragraph_end_text_offset)(__builtin_expect (__extension__ ({ int _g_boolean_var_82 = 0 ; 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; _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 = 0 ; if (!cell->attr.fragment()) _g_boolean_var_83 = 1; _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 | } |