diff options
author | pommicket <pommicket@gmail.com> | 2023-01-04 20:15:45 -0500 |
---|---|---|
committer | pommicket <pommicket@gmail.com> | 2023-01-04 20:15:55 -0500 |
commit | 808b9a13cb5c71c28db6c842b78ef7f1743283cd (patch) | |
tree | 1f57a266294c9fc99673981403361c2d5507d1ba | |
parent | d9cc57e9ff1725e6e63973705adbf218d6961d17 (diff) |
the great "filename to path" rename
-rw-r--r-- | buffer.c | 116 | ||||
-rw-r--r-- | command.c | 7 | ||||
-rw-r--r-- | ide-autocomplete.c | 7 | ||||
-rw-r--r-- | main.c | 10 | ||||
-rw-r--r-- | menu.c | 2 | ||||
-rw-r--r-- | node.c | 3 | ||||
-rw-r--r-- | session.c | 8 | ||||
-rw-r--r-- | ted.c | 22 | ||||
-rw-r--r-- | ted.h | 16 |
9 files changed, 108 insertions, 83 deletions
@@ -4,7 +4,7 @@ #include "ted.h" - +#define BUFFER_UNTITLED "Untitled" // what to call untitled buffers // this is a macro so we get -Wformat warnings #define buffer_error(buffer, ...) \ @@ -60,15 +60,12 @@ bool buffer_empty(TextBuffer *buffer) { return buffer->nlines == 1 && buffer->lines[0].len == 0; } -bool buffer_is_untitled(TextBuffer *buffer) { - if (buffer->filename) - return streq(buffer->filename, TED_UNTITLED); - else - return false; +bool buffer_is_named_file(TextBuffer *buffer) { + return buffer->path != NULL; } -bool buffer_is_named_file(TextBuffer *buffer) { - return buffer->filename && !buffer_is_untitled(buffer); +const char *buffer_display_filename(TextBuffer *buffer) { + return buffer->path ? path_filename(buffer->path) : BUFFER_UNTITLED; } // add this edit to the undo history @@ -170,7 +167,7 @@ bool buffer_pos_valid(TextBuffer *buffer, BufferPos p) { // are there any unsaved changes? bool buffer_unsaved_changes(TextBuffer *buffer) { - if (buffer_is_untitled(buffer) && buffer_empty(buffer)) + if (!buffer->path && buffer_empty(buffer)) return false; // don't worry about empty untitled buffers return arr_len(buffer->undo_history) != buffer->undo_history_write_pos; } @@ -220,14 +217,15 @@ static Font *buffer_font(TextBuffer *buffer) { // what programming language is this? Language buffer_language(TextBuffer *buffer) { + if (!buffer->path) + return LANG_NONE; + // @TODO(optimization): cache this? // (we're calling buffer_lsp on every edit and that calls this) if (buffer->manual_language >= 1 && buffer->manual_language <= LANG_COUNT) return (Language)(buffer->manual_language - 1); const Settings *settings = buffer->ted->default_settings; // important we don't use buffer_settings here since that would cause a loop! - const char *filename = buffer->filename; - if (!filename) - return LANG_NONE; + const char *filename = path_filename(buffer->path); size_t filename_len = strlen(filename); int match_score = 0; @@ -257,11 +255,15 @@ Language buffer_language(TextBuffer *buffer) { return match; } -// set filename = NULL to default to buffer->filename -static void buffer_send_lsp_did_close(TextBuffer *buffer, LSP *lsp, const char *filename) { +// set path = NULL to default to buffer->path +static void buffer_send_lsp_did_close(TextBuffer *buffer, LSP *lsp, const char *path) { + if (path && !path_is_absolute(path)) { + assert(0); + return; + } LSPRequest did_close = {.type = LSP_REQUEST_DID_CLOSE}; did_close.data.close = (LSPRequestDidClose){ - .document = lsp_document_id(lsp, filename ? filename : buffer->filename) + .document = lsp_document_id(lsp, path ? path : buffer->path) }; lsp_send_request(lsp, &did_close); buffer->lsp_opened_in = 0; @@ -276,7 +278,7 @@ static void buffer_send_lsp_did_open(TextBuffer *buffer, LSP *lsp, char *buffer_ LSPRequest request = {.type = LSP_REQUEST_DID_OPEN}; LSPRequestDidOpen *open = &request.data.open; open->file_contents = buffer_contents; - open->document = lsp_document_id(lsp, buffer->filename); + open->document = lsp_document_id(lsp, buffer->path); open->language = buffer_language(buffer); lsp_send_request(lsp, &request); buffer->lsp_opened_in = lsp->id; @@ -289,7 +291,7 @@ LSP *buffer_lsp(TextBuffer *buffer) { return NULL; if (buffer->view_only) return NULL; // we don't really want to start up an LSP in /usr/include - LSP *true_lsp = ted_get_lsp(buffer->ted, buffer->filename, buffer_language(buffer)); + LSP *true_lsp = ted_get_lsp(buffer->ted, buffer->path, buffer_language(buffer)); LSP *curr_lsp = ted_get_lsp_by_id(buffer->ted, buffer->lsp_opened_in); if (true_lsp != curr_lsp) { if (curr_lsp) @@ -303,7 +305,7 @@ LSP *buffer_lsp(TextBuffer *buffer) { Settings *buffer_settings(TextBuffer *buffer) { - return ted_get_settings(buffer->ted, buffer->filename, buffer_language(buffer)); + return ted_get_settings(buffer->ted, buffer->path, buffer_language(buffer)); } @@ -723,7 +725,7 @@ void buffer_free(TextBuffer *buffer) { buffer_line_free(&lines[i]); } free(lines); - free(buffer->filename); + free(buffer->path); arr_foreach_ptr(buffer->undo_history, BufferEdit, edit) buffer_edit_free(edit); @@ -1434,7 +1436,7 @@ static Status buffer_insert_lines(TextBuffer *buffer, u32 where, u32 number) { LSPDocumentID buffer_lsp_document_id(TextBuffer *buffer) { LSP *lsp = buffer_lsp(buffer); - return lsp ? lsp_document_id(lsp, buffer->filename) : 0; + return lsp ? lsp_document_id(lsp, buffer->path) : 0; } // LSP uses UTF-16 indices because Microsoft fucking loves UTF-16 and won't let it die @@ -1499,7 +1501,7 @@ static void buffer_send_lsp_did_change(LSP *lsp, TextBuffer *buffer, BufferPos p event.range.start = buffer_pos_to_lsp_position(buffer, pos); BufferPos pos_end = buffer_pos_advance(buffer, pos, nchars_deleted); event.range.end = buffer_pos_to_lsp_position(buffer, pos_end); - lsp_document_changed(lsp, buffer->filename, event); + lsp_document_changed(lsp, buffer->path, event); } // inserts the given text, returning the position of the end of the text @@ -2322,8 +2324,8 @@ Status buffer_load_file(TextBuffer *buffer, const char *path) { } if (success) { - char *filename_copy = buffer_strdup(buffer, path); - if (!filename_copy) success = false; + char *path_copy = buffer_strdup(buffer, path); + if (!path_copy) success = false; if (success) { // everything is good buffer_clear(buffer); @@ -2332,7 +2334,7 @@ Status buffer_load_file(TextBuffer *buffer, const char *path) { buffer->frame_earliest_line_modified = 0; buffer->frame_latest_line_modified = nlines - 1; buffer->lines_capacity = lines_capacity; - buffer->filename = filename_copy; + buffer->path = path_copy; buffer->last_write_time = modified_time; if (!(fs_path_permission(path) & FS_PERMISSION_WRITE)) { // can't write to this file; make the buffer view only. @@ -2363,12 +2365,12 @@ Status buffer_load_file(TextBuffer *buffer, const char *path) { // Reloads the file loaded in the buffer. // Note that this clears undo history, etc. void buffer_reload(TextBuffer *buffer) { - if (buffer->filename && !buffer_is_untitled(buffer)) { + if (buffer_is_named_file(buffer)) { BufferPos cursor_pos = buffer->cursor_pos; float x1 = buffer->x1, y1 = buffer->y1, x2 = buffer->x2, y2 = buffer->y2; double scroll_x = buffer->scroll_x; double scroll_y = buffer->scroll_y; - char *filename = str_dup(buffer->filename); - if (buffer_load_file(buffer, filename)) { + char *path = str_dup(buffer->path); + if (buffer_load_file(buffer, path)) { buffer->x1 = x1; buffer->y1 = y1; buffer->x2 = x2; buffer->y2 = y2; buffer->cursor_pos = cursor_pos; buffer->scroll_x = scroll_x; @@ -2376,7 +2378,7 @@ void buffer_reload(TextBuffer *buffer) { buffer_validate_cursor(buffer); buffer_correct_scroll(buffer); } - free(filename); + free(path); } } @@ -2384,31 +2386,34 @@ void buffer_reload(TextBuffer *buffer) { bool buffer_externally_changed(TextBuffer *buffer) { if (!buffer_is_named_file(buffer)) return false; - return buffer->last_write_time != timespec_to_seconds(time_last_modified(buffer->filename)); + return buffer->last_write_time != timespec_to_seconds(time_last_modified(buffer->path)); } -void buffer_new_file(TextBuffer *buffer, const char *filename) { +void buffer_new_file(TextBuffer *buffer, const char *path) { + if (path && !path_is_absolute(path)) { + buffer_error(buffer, "Cannot create %s: path is not absolute", path); + return; + } + buffer_clear(buffer); - if (filename) - buffer->filename = buffer_strdup(buffer, filename); + if (path) + buffer->path = buffer_strdup(buffer, path); buffer->lines_capacity = 4; buffer->lines = buffer_calloc(buffer, buffer->lines_capacity, sizeof *buffer->lines); buffer->nlines = 1; } -// Save the buffer to its current filename. This will rewrite the entire file, regardless of -// whether there are any unsaved changes. bool buffer_save(TextBuffer *buffer) { const Settings *settings = buffer_settings(buffer); - if (!buffer->is_line_buffer && buffer->filename) { + if (buffer_is_named_file(buffer)) { if (buffer->view_only) { buffer_error(buffer, "Can't save view-only file."); return false; } - FILE *out = fopen(buffer->filename, "wb"); + FILE *out = fopen(buffer->path, "wb"); if (out) { if (settings->auto_add_newline) { Line *last_line = &buffer->lines[buffer->nlines - 1]; @@ -2427,7 +2432,7 @@ bool buffer_save(TextBuffer *buffer) { size_t bytes = unicode_utf32_to_utf8(utf8, *p); if (bytes != (size_t)-1) { if (fwrite(utf8, 1, bytes, out) != bytes) { - buffer_error(buffer, "Couldn't write to %s.", buffer->filename); + buffer_error(buffer, "Couldn't write to %s.", buffer->path); } } } @@ -2438,24 +2443,24 @@ bool buffer_save(TextBuffer *buffer) { } if (ferror(out)) { if (!buffer_has_error(buffer)) - buffer_error(buffer, "Couldn't write to %s.", buffer->filename); + buffer_error(buffer, "Couldn't write to %s.", buffer->path); } if (fclose(out) != 0) { if (!buffer_has_error(buffer)) - buffer_error(buffer, "Couldn't close file %s.", buffer->filename); + buffer_error(buffer, "Couldn't close file %s.", buffer->path); } - buffer->last_write_time = timespec_to_seconds(time_last_modified(buffer->filename)); + buffer->last_write_time = timespec_to_seconds(time_last_modified(buffer->path)); bool success = !buffer_has_error(buffer); if (success) { buffer->undo_history_write_pos = arr_len(buffer->undo_history); - const char *name = buffer->filename ? path_filename(buffer->filename) : TED_UNTITLED; - if (streq(name, "ted.cfg") && buffer_settings(buffer)->auto_reload_config) { + if (buffer->path && streq(path_filename(buffer->path), "ted.cfg") + && buffer_settings(buffer)->auto_reload_config) { ted_load_configs(buffer->ted, true); } } return success; } else { - buffer_error(buffer, "Couldn't open file %s for writing: %s.", buffer->filename, strerror(errno)); + buffer_error(buffer, "Couldn't open file %s for writing: %s.", buffer->path, strerror(errno)); return false; } } else { @@ -2464,26 +2469,31 @@ bool buffer_save(TextBuffer *buffer) { } } -// save, but with a different file name -bool buffer_save_as(TextBuffer *buffer, const char *new_filename) { +bool buffer_save_as(TextBuffer *buffer, const char *new_path) { + if (!path_is_absolute(new_path)) { + assert(0); + buffer_error(buffer, "New path %s is not absolute.", new_path); + return false; + } + LSP *lsp = buffer_lsp(buffer); - char *prev_filename = buffer->filename; - buffer->filename = buffer_strdup(buffer, new_filename); + char *prev_path = buffer->path; + buffer->path = buffer_strdup(buffer, new_path); - if (buffer->filename && buffer_save(buffer)) { + if (buffer->path && buffer_save(buffer)) { buffer->view_only = false; - // ensure whole file is syntax highlighted when saving with a different + // ensure whole file is re-highlighted when saving with a different // file extension buffer->frame_earliest_line_modified = 0; buffer->frame_latest_line_modified = buffer->nlines - 1; if (lsp) - buffer_send_lsp_did_close(buffer, lsp, prev_filename); + buffer_send_lsp_did_close(buffer, lsp, prev_path); // we'll send a didOpen the next time buffer_lsp is called. - free(prev_filename); + free(prev_path); return true; } else { - free(buffer->filename); - buffer->filename = prev_filename; + free(buffer->path); + buffer->path = prev_path; return false; } } @@ -341,7 +341,7 @@ void command_execute(Ted *ted, Command c, i64 argument) { case CMD_SAVE: ted->last_save_time = ted->frame_time; if (buffer) { - if (buffer_is_untitled(buffer)) { + if (!buffer->path) { command_execute(ted, CMD_SAVE_AS, 1); return; } @@ -374,7 +374,8 @@ void command_execute(Ted *ted, Command c, i64 argument) { if (buffers_used[i]) { buffer = &ted->buffers[i]; if (buffer_unsaved_changes(buffer)) { - strbuf_catf(ted->warn_unsaved_names, "%s%s", first ? "" : ", ", path_filename(buffer->filename)); + const char *path = buffer_display_filename(buffer); + strbuf_catf(ted->warn_unsaved_names, "%s%s", first ? "" : ", ", path); first = false; } } @@ -484,7 +485,7 @@ void command_execute(Ted *ted, Command c, i64 argument) { if (argument != 2 && buffer_unsaved_changes(buffer)) { // there are unsaved changes! ted->warn_unsaved = CMD_TAB_CLOSE; - strbuf_printf(ted->warn_unsaved_names, "%s", path_filename(buffer->filename)); + strbuf_printf(ted->warn_unsaved_names, "%s", buffer_display_filename(buffer)); menu_open(ted, MENU_WARN_UNSAVED); } else { node_tab_close(ted, node, node->active_tab); diff --git a/ide-autocomplete.c b/ide-autocomplete.c index 39ee076..9f70758 100644 --- a/ide-autocomplete.c +++ b/ide-autocomplete.c @@ -112,6 +112,9 @@ static void autocomplete_no_suggestions(Ted *ted) { } static void autocomplete_send_completion_request(Ted *ted, TextBuffer *buffer, BufferPos pos, uint32_t trigger) { + if (!buffer->path) + return; // no can do + LSP *lsp = buffer_lsp(buffer); Autocomplete *ac = &ted->autocomplete; @@ -129,7 +132,7 @@ static void autocomplete_send_completion_request(Ted *ted, TextBuffer *buffer, B request.data.completion = (LSPRequestCompletion) { .position = { - .document = lsp_document_id(lsp, buffer->filename), + .document = lsp_document_id(lsp, buffer->path), .pos = buffer_pos_to_lsp_position(buffer, pos) }, .context = { @@ -295,7 +298,7 @@ void autocomplete_open(Ted *ted, uint32_t trigger) { if (ac->open) return; if (!ted->active_buffer) return; TextBuffer *buffer = ted->active_buffer; - if (!buffer->filename) return; + if (!buffer->path) return; if (buffer->view_only) return; ted->cursor_error_time = 0; @@ -17,15 +17,17 @@ - some way of opening + closing all C files in directory for clangd textDocument/references to work? - maybe it can be done with the clangd config instead. + - does vscode have the same problem? - more documentation generally (development.md or something?) -- rename buffer->filename to buffer->path - - make buffer->path NULL for untitled buffers & fix resulting mess - rust-analyzer bug reports: - bad json can give "Unexpected error: client exited without proper shutdown sequence" - containerName not always given - clangd bug report: - textDocumemt/definition on ted.h declarations just gives you the declaration FUTURE FEATURES: +- write first to <path>.tmp then rename to <path>. + this prevents freak occurences, e.g. power outage during file write, + from losing (all) data. - better handling of backspace with space indentation - CSS highlighting - styles ([color] sections) @@ -770,7 +772,7 @@ int main(int argc, char **argv) { TextBuffer *buffer = ted->active_buffer; if (buffer) { if (buffer_is_named_file(buffer)) { - const char *buffer_path = buffer->filename; + const char *buffer_path = buffer->path; assert(*buffer_path); char *last_sep = strrchr(buffer_path, PATH_SEPARATOR); if (last_sep) { @@ -791,7 +793,7 @@ int main(int argc, char **argv) { if (buffer_settings(active_buffer)->auto_reload) buffer_reload(active_buffer); else { - strbuf_cpy(ted->ask_reload, active_buffer->filename); + strbuf_cpy(ted->ask_reload, buffer_display_filename(active_buffer)); menu_open(ted, MENU_ASK_RELOAD); } } @@ -199,7 +199,7 @@ void menu_update(Ted *ted) { case POPUP_NO: menu_close(ted); if (buffer) - buffer->last_write_time = timespec_to_seconds(time_last_modified(buffer->filename)); + buffer->last_write_time = timespec_to_seconds(time_last_modified(buffer->path)); break; case POPUP_CANCEL: assert(0); break; } @@ -293,8 +293,7 @@ void node_frame(Ted *ted, Node *node, Rect r) { for (u16 i = 0; i < ntabs; ++i) { TextBuffer *buffer = &ted->buffers[node->tabs[i]]; char tab_title[256]; - const char *path = buffer->filename; - const char *filename = path ? path_filename(path) : TED_UNTITLED; + const char *filename = buffer_display_filename(buffer); Rect tab_rect = rect(Vec2(r.pos.x + tab_width * i, r.pos.y), Vec2(tab_width, tab_bar_height)); if (i > 0) { @@ -206,8 +206,8 @@ static void session_write_buffer(Ted *ted, FILE *fp, u16 buffer_idx) { write_u16(fp, buffer_idx); TextBuffer *buffer = &ted->buffers[buffer_idx]; // some info about the buffer that should be restored - if (buffer->filename && !buffer_is_untitled(buffer)) - write_cstr(fp, buffer->filename); + if (buffer_is_named_file(buffer)) + write_cstr(fp, buffer->path); else write_char(fp, 0); write_double(fp, buffer->scroll_x); @@ -233,9 +233,9 @@ static void session_read_buffer(Ted *ted, FILE *fp) { if (!buffer_has_error(buffer)) { if (*filename) { if (!buffer_load_file(buffer, filename)) - buffer_new_file(buffer, TED_UNTITLED); + buffer_new_file(buffer, NULL); } else { - buffer_new_file(buffer, TED_UNTITLED); + buffer_new_file(buffer, NULL); } buffer->scroll_x = read_double(fp); buffer->scroll_y = read_double(fp); @@ -110,7 +110,7 @@ char *ted_get_root_dir_of(Ted *ted, const char *path) { char *ted_get_root_dir(Ted *ted) { TextBuffer *buffer = ted->active_buffer; if (buffer) { - return ted_get_root_dir_of(ted, buffer->filename); + return ted_get_root_dir_of(ted, buffer->path); } else { return ted_get_root_dir_of(ted, ted->cwd); } @@ -407,11 +407,13 @@ static Status ted_open_buffer(Ted *ted, u16 *buffer_idx, u16 *tab) { } TextBuffer *ted_get_buffer_with_file(Ted *ted, const char *path) { + if (!path) return NULL; + bool *buffers_used = ted->buffers_used; TextBuffer *buffers = ted->buffers; for (u16 i = 0; i < TED_MAX_BUFFERS; ++i) { if (buffers_used[i]) { - if (buffers[i].filename && paths_eq(path, buffers[i].filename)) { + if (buffers[i].path && paths_eq(path, buffers[i].path)) { return &buffers[i]; } } @@ -432,7 +434,7 @@ bool ted_open_file(Ted *ted, const char *filename) { // not open; we need to load it u16 buffer_idx, tab_idx; - if (ted->active_buffer && buffer_is_untitled(ted->active_buffer) && buffer_empty(ted->active_buffer)) { + if (ted->active_buffer && !ted->active_buffer->path && buffer_empty(ted->active_buffer)) { // the active buffer is just an empty untitled buffer. open it here. return buffer_load_file(ted->active_buffer, path); } else if (ted_open_buffer(ted, &buffer_idx, &tab_idx)) { @@ -456,10 +458,14 @@ bool ted_new_file(Ted *ted, const char *filename) { if (filename) ted_path_full(ted, filename, path, sizeof path); else - strbuf_cpy(path, TED_UNTITLED); - if (ted_open_buffer(ted, &buffer_idx, &tab_idx)) { - TextBuffer *buffer = &ted->buffers[buffer_idx]; - buffer_new_file(buffer, path); + *path = '\0'; + TextBuffer *buffer = ted_get_buffer_with_file(ted, path); + if (buffer) { + ted_switch_to_buffer(ted, buffer); + return true; + } else if (ted_open_buffer(ted, &buffer_idx, &tab_idx)) { + buffer = &ted->buffers[buffer_idx]; + buffer_new_file(buffer, *path ? path : NULL); if (!buffer_has_error(buffer)) { return true; } else { @@ -481,7 +487,7 @@ bool ted_save_all(Ted *ted) { if (buffers_used[i]) { TextBuffer *buffer = &ted->buffers[i]; if (buffer_unsaved_changes(buffer)) { - if (buffer->filename && buffer_is_untitled(buffer)) { + if (!buffer->path) { ted_switch_to_buffer(ted, buffer); menu_open(ted, MENU_SAVE_AS); success = false; // we haven't saved this buffer yet; we've just opened the "save as" menu. @@ -19,7 +19,6 @@ #define TED_VERSION "2.0" #define TED_VERSION_FULL "ted v. " TED_VERSION #define TED_PATH_MAX 256 -#define TED_UNTITLED "Untitled" // what to call untitled buffers #define TED_CFG "ted.cfg" // config filename #define TEXT_SIZE_MIN 6 @@ -255,10 +254,10 @@ typedef struct { // a buffer - this includes line buffers, unnamed buffers, the build buffer, etc. typedef struct { - char *filename; // NULL if this buffer doesn't correspond to a file (e.g. line buffers) + char *path; // NULL if this buffer is untitled or doesn't correspond to a file (e.g. line buffers) struct Ted *ted; // we keep a back-pointer to the ted instance so we don't have to pass it in to every buffer function double scroll_x, scroll_y; // number of characters scrolled in the x/y direction - double last_write_time; // last write time to filename. + double last_write_time; // last write time to `path`. i16 manual_language; // 1 + the language the buffer has been manually set to, or 0 if it hasn't been manually set to anything BufferPos cursor_pos; BufferPos selection_pos; // if selection is true, the text between selection_pos and cursor_pos is selected. @@ -669,8 +668,8 @@ void buffer_clear_error(TextBuffer *buffer); void buffer_clear_undo_redo(TextBuffer *buffer); // is this buffer empty? bool buffer_empty(TextBuffer *buffer); -// is this buffer normal (i.e. not a line buffer or build buffer), but untitled? -bool buffer_is_untitled(TextBuffer *buffer); +// returns the buffer's filename (not full path), or "Untitled" if this buffer is untitled. +const char *buffer_display_filename(TextBuffer *buffer); // does this buffer contained a named file (i.e. not a line buffer, not the build buffer, not untitled) bool buffer_is_named_file(TextBuffer *buffer); // create a new empty buffer with no file name @@ -928,8 +927,13 @@ void buffer_paste(TextBuffer *buffer); bool buffer_load_file(TextBuffer *buffer, const char *path); void buffer_reload(TextBuffer *buffer); bool buffer_externally_changed(TextBuffer *buffer); -void buffer_new_file(TextBuffer *buffer, const char *filename); +// Clear `buffer`, and set its path to `path`. +// if `path` is NULL, this will turn `buffer` into an untitled buffer. +void buffer_new_file(TextBuffer *buffer, const char *path); +// Save the buffer to its current filename. This will rewrite the entire file, +// even if there are no unsaved changes. bool buffer_save(TextBuffer *buffer); +// save, but with a different path bool buffer_save_as(TextBuffer *buffer, const char *new_filename); u32 buffer_first_rendered_line(TextBuffer *buffer); u32 buffer_last_rendered_line(TextBuffer *buffer); |