diff options
author | Leo Tenenbaum <pommicket@gmail.com> | 2021-03-01 15:02:40 -0500 |
---|---|---|
committer | Leo Tenenbaum <pommicket@gmail.com> | 2021-03-01 15:02:40 -0500 |
commit | 12bb6e2cd377d3dfa2f7ec037c31463c3f9070d1 (patch) | |
tree | e3311ad77bd0fb768adcdce7582397cf97f40183 | |
parent | 2d327302b30bf67a87538dc5e005c0399c0b8f1a (diff) |
restore session
-rw-r--r-- | buffer.c | 16 | ||||
-rw-r--r-- | command.c | 5 | ||||
-rw-r--r-- | config.c | 1 | ||||
-rw-r--r-- | io.c | 135 | ||||
-rw-r--r-- | main.c | 49 | ||||
-rw-r--r-- | node.c | 2 | ||||
-rw-r--r-- | session.c | 173 | ||||
-rw-r--r-- | ted.c | 24 | ||||
-rw-r--r-- | ted.cfg | 2 | ||||
-rw-r--r-- | ted.h | 2 | ||||
-rw-r--r-- | util.c | 4 |
11 files changed, 368 insertions, 45 deletions
@@ -112,7 +112,6 @@ void buffer_create(TextBuffer *buffer, Ted *ted) { buffer->ted = ted; } - void line_buffer_create(TextBuffer *buffer, Ted *ted) { buffer_create(buffer, ted); buffer->is_line_buffer = true; @@ -142,6 +141,21 @@ static bool buffer_pos_valid(TextBuffer *buffer, BufferPos p) { return p.line < buffer->nlines && p.index <= buffer->lines[p.line].len; } +// write a buffer position to a file +void buffer_pos_write(BufferPos pos, FILE *fp) { + write_u32(fp, pos.line); + write_u32(fp, pos.index); +} + +// read a buffer position from a file, and validate it +BufferPos buffer_pos_read(TextBuffer *buffer, FILE *fp) { + BufferPos pos = {0}; + pos.line = read_u32(fp); + pos.index = read_u32(fp); + buffer_pos_validate(buffer, &pos); + return pos; +} + // are there any unsaved changes? bool buffer_unsaved_changes(TextBuffer *buffer) { if (buffer_is_untitled(buffer) && buffer_empty(buffer)) @@ -155,12 +155,11 @@ void command_execute(Ted *ted, Command c, i64 argument) { menu_open(ted, MENU_OPEN); break; case CMD_NEW: - ted_new_file(ted); + ted_new_file(ted, NULL); break; case CMD_SAVE: if (buffer) { - if (buffer->filename && streq(buffer->filename, TED_UNTITLED)) { - // don't worry, this won't catch files called "Untitled"; buffer->filename is the full path. + if (buffer_is_untitled(buffer)) { command_execute(ted, CMD_SAVE_AS, 1); return; } @@ -182,6 +182,7 @@ void config_read(Ted *ted, char const *filename) { {"auto-reload", &settings->auto_reload}, {"syntax-highlighting", &settings->syntax_highlighting}, {"line-numbers", &settings->line_numbers}, + {"restore-session", &settings->restore_session}, }; OptionU8 const options_u8[] = { {"tab-width", &settings->tab_width, 1, 100}, @@ -0,0 +1,135 @@ +static void write_u8(FILE *fp, u8 x) { + putc(x, fp); +} + +static void write_u16(FILE *fp, u16 x) { + fwrite(&x, sizeof x, 1, fp); +} + +static void write_u32(FILE *fp, u32 x) { + fwrite(&x, sizeof x, 1, fp); +} + +static void write_u64(FILE *fp, u64 x) { + fwrite(&x, sizeof x, 1, fp); +} + +static void write_i8(FILE *fp, i8 x) { + fwrite(&x, sizeof x, 1, fp); +} + +static void write_i16(FILE *fp, i16 x) { + fwrite(&x, sizeof x, 1, fp); +} + +static void write_i32(FILE *fp, i32 x) { + fwrite(&x, sizeof x, 1, fp); +} + +static void write_i64(FILE *fp, i64 x) { + fwrite(&x, sizeof x, 1, fp); +} + +static void write_float(FILE *fp, float x) { + fwrite(&x, sizeof x, 1, fp); +} + +static void write_double(FILE *fp, double x) { + fwrite(&x, sizeof x, 1, fp); +} + +static void write_char(FILE *fp, char x) { + fwrite(&x, sizeof x, 1, fp); +} + +static void write_bool(FILE *fp, bool x) { + putc(x, fp); +} + +static u8 read_u8(FILE *fp) { + return (u8)getc(fp); +} + +static u16 read_u16(FILE *fp) { + u16 x = 0; + fread(&x, sizeof x, 1, fp); + return x; +} + +static u32 read_u32(FILE *fp) { + u32 x = 0; + fread(&x, sizeof x, 1, fp); + return x; +} + +static u64 read_u64(FILE *fp) { + u64 x = 0; + fread(&x, sizeof x, 1, fp); + return x; +} + +static i8 read_i8(FILE *fp) { + i8 x = 0; + fread(&x, sizeof x, 1, fp); + return x; +} + +static i16 read_i16(FILE *fp) { + i16 x = 0; + fread(&x, sizeof x, 1, fp); + return x; +} + +static i32 read_i32(FILE *fp) { + i32 x = 0; + fread(&x, sizeof x, 1, fp); + return x; +} + +static i64 read_i64(FILE *fp) { + i64 x = 0; + fread(&x, sizeof x, 1, fp); + return x; +} + +static float read_float(FILE *fp) { + float x = 0; + fread(&x, sizeof x, 1, fp); + return x; +} + +static double read_double(FILE *fp) { + double x = 0; + fread(&x, sizeof x, 1, fp); + return x; +} + +static char read_char(FILE *fp) { + char x = 0; + fread(&x, sizeof x, 1, fp); + return x; +} + +static bool read_bool(FILE *fp) { + return (bool)getc(fp); +} + +static void write_cstr(FILE *fp, char const *cstr) { + fwrite(cstr, 1, strlen(cstr) + 1, fp); +} + +static void read_cstr(FILE *fp, char *out, size_t out_sz) { + char *p = out, *end = out + out_sz; + while (1) { + if (p >= end - 1) { + *p = '\0'; + break; + } + int c = getc(fp); + if (c == 0 || c == EOF) { + *p = '\0'; + break; + } + *p++ = (char)c; + } +} @@ -1,7 +1,6 @@ // @TODO: // - "on crash, output backtrace to log" for Windows // - Windows installation -// - restore previously opened files (setting: restore-session) // - test on BSD // - completion @@ -49,6 +48,8 @@ no_warn_end #error "Unrecognized operating system." #endif +#include "io.c" + #include "text.h" #include "command.h" #include "colors.h" @@ -70,6 +71,7 @@ bool tag_goto(Ted *ted, char const *tag); #include "build.c" #include "command.c" #include "config.c" +#include "session.c" #if PROFILE #define PROFILE_TIME(var) double var = time_get_seconds(); @@ -114,8 +116,11 @@ static void APIENTRY gl_message_callback(GLenum source, GLenum type, unsigned in #if __unix__ static Ted *error_signal_handler_ted; +static bool signal_being_handled; // prevent infinite signal recursion static void error_signal_handler(int signum, siginfo_t *info, void *context) { (void)context; + if (signal_being_handled) + die("ted crashed while trying to handle a crash! yikes! ):"); Ted *ted = error_signal_handler_ted; if (ted) { FILE *log = ted->log; @@ -142,6 +147,7 @@ static void error_signal_handler(int signum, siginfo_t *info, void *context) { #endif fclose(log); } + session_write(ted); die("ted has crashed ): Please send %s/log.txt to pommicket""@gmail.com if you want this fixed.", ted->local_data_dir); } else { @@ -427,38 +433,16 @@ int main(int argc, char **argv) { buffer_create(&ted->build_buffer, ted); { - u16 buffer_index = (u16)ted_new_buffer(ted); - assert(buffer_index == 1); - TextBuffer *buffer = &ted->buffers[buffer_index]; - buffer_create(buffer, ted); - u16 node_index = (u16)ted_new_node(ted); - assert(node_index == 0); - Node *node = &ted->nodes[node_index]; - node->tabs = NULL; - arr_add(node->tabs, buffer_index); - - ted_switch_to_buffer(ted, buffer); - - - char path[TED_PATH_MAX]; if (starting_filename) { - // get full path to file - if (!path_is_absolute(starting_filename)) - strbuf_printf(path, "%s%s%s", ted->cwd, ted->cwd[strlen(ted->cwd) - 1] == PATH_SEPARATOR ? "" : PATH_SEPARATOR_STR, - starting_filename); - else - strbuf_printf(path, "%s", starting_filename); - } else { - strbuf_printf(path, "Untitled"); - } - - if (starting_filename && fs_file_exists(path)) { - if (!buffer_load_file(buffer, path)) - ted_seterr(ted, "Couldn't load file: %s", buffer_geterr(buffer)); + if (fs_file_exists(starting_filename)) { + if (!ted_open_file(ted, starting_filename)) + ted_seterr(ted, "Couldn't load file: %s", ted_geterr(ted)); + } else { + if (!ted_new_file(ted, starting_filename)) + ted_seterr(ted, "Couldn't create file: %s", ted_geterr(ted)); + } } else { - buffer_new_file(buffer, path); - if (buffer_haserr(buffer)) - ted_seterr(ted, "Couldn't create file: %s", buffer_geterr(buffer)); + session_read(ted); } } @@ -472,6 +456,8 @@ int main(int argc, char **argv) { ted->cursor_hand = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND); ted->cursor_move = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL); + + Uint32 time_at_last_frame = SDL_GetTicks(); while (!ted->quit) { @@ -827,6 +813,7 @@ int main(int argc, char **argv) { } + session_write(ted); SDL_FreeCursor(ted->cursor_arrow); SDL_FreeCursor(ted->cursor_ibeam); @@ -271,7 +271,7 @@ static void node_frame(Ted *ted, Node *node, Rect r) { TextBuffer *buffer = &ted->buffers[node->tabs[i]]; char tab_title[256]; char const *path = buffer_get_filename(buffer); - char const *filename = path_filename(path); + char const *filename = path ? path_filename(path) : TED_UNTITLED; Rect tab_rect = rect(V2(r.pos.x + tab_width * i, r.pos.y), V2(tab_width, tab_bar_height)); if (node == ted->dragging_tab_node && i == ted->dragging_tab_idx) { diff --git a/session.c b/session.c new file mode 100644 index 0000000..cbd84b0 --- /dev/null +++ b/session.c @@ -0,0 +1,173 @@ +#define SESSION_FILENAME "session.txt" +#define SESSION_VERSION "\x7fTED0001" + +static void session_write_node(Ted *ted, FILE *fp, u16 node_idx) { + Node *node = &ted->nodes[node_idx]; + write_u16(fp, node_idx); + bool is_split = !node->tabs; + write_bool(fp, is_split); + if (is_split) { + write_float(fp, node->split_pos); + write_bool(fp, node->split_vertical); + write_u16(fp, node->split_a); + write_u16(fp, node->split_b); + } else { + write_u16(fp, node->active_tab); // active tab + write_u16(fp, (u16)arr_len(node->tabs)); // ntabs + arr_foreach_ptr(node->tabs, u16, tab) { + write_u16(fp, *tab); + } + } +} + +static void session_read_node(Ted *ted, FILE *fp) { + u16 node_idx = read_u16(fp); + if (node_idx >= TED_MAX_NODES) { + debug_println("WARNING: Invalid node index (see %s:%d)!\n", __FILE__, __LINE__); + return; + } + ted->nodes_used[node_idx] = true; + Node *node = &ted->nodes[node_idx]; + bool is_split = read_bool(fp); + if (is_split) { + node->split_pos = clampf(read_float(fp), 0, 1); + node->split_vertical = read_bool(fp); + node->split_a = clamp_u16(read_u16(fp), 0, TED_MAX_NODES); + node->split_b = clamp_u16(read_u16(fp), 0, TED_MAX_NODES); + } else { + node->active_tab = read_u16(fp); + u16 ntabs = clamp_u16(read_u16(fp), 0, TED_MAX_TABS); + for (u16 i = 0; i < ntabs; ++i) { + u16 buf_idx = read_u16(fp); + if (buf_idx >= TED_MAX_BUFFERS) continue; + arr_add(node->tabs, buf_idx); + } + } +} + +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); + else + write_char(fp, 0); + write_double(fp, buffer->scroll_x); + write_double(fp, buffer->scroll_y); + write_bool(fp, buffer->view_only); + buffer_pos_write(buffer->cursor_pos, fp); + write_bool(fp, buffer->selection); + if (buffer->selection) + buffer_pos_write(buffer->selection_pos, fp); +} + +static void session_read_buffer(Ted *ted, FILE *fp) { + u16 buffer_idx = read_u16(fp); + if (buffer_idx >= TED_MAX_BUFFERS) { + debug_println("WARNING: Invalid buffer index (see %s:%d)!\n", __FILE__, __LINE__); + return; + } + TextBuffer *buffer = &ted->buffers[buffer_idx]; + ted->buffers_used[buffer_idx] = true; + char filename[TED_PATH_MAX] = {0}; + read_cstr(fp, filename, sizeof filename); + buffer_create(buffer, ted); + if (!buffer_haserr(buffer)) { + if (*filename) { + if (!buffer_load_file(buffer, filename)) + buffer_new_file(buffer, TED_UNTITLED); + } else { + buffer_new_file(buffer, TED_UNTITLED); + } + buffer->scroll_x = read_double(fp); + buffer->scroll_y = read_double(fp); + buffer->view_only = read_bool(fp); + buffer->cursor_pos = buffer_pos_read(buffer, fp); + buffer->selection = read_bool(fp); + if (buffer->selection) + buffer->selection_pos = buffer_pos_read(buffer, fp); + } +} + +static void session_write_file(Ted *ted, FILE *fp) { + fwrite(SESSION_VERSION, 1, sizeof SESSION_VERSION, fp); + + write_u16(fp, (u16)(ted->active_node - ted->nodes)); // active node idx + write_u16(fp, (u16)(ted->active_buffer - ted->buffers)); // active buffer idx + + u16 nnodes = 0; + for (u16 i = 0; i < TED_MAX_NODES; ++i) + nnodes += ted->nodes_used[i]; + write_u16(fp, nnodes); + for (u16 i = 0; i < TED_MAX_NODES; ++i) + if (ted->nodes_used[i]) + session_write_node(ted, fp, i); + + u16 nbuffers = 0; + for (u16 i = 0; i < TED_MAX_BUFFERS; ++i) + nbuffers += ted->buffers_used[i]; + write_u16(fp, nbuffers); + for (u16 i = 0; i < TED_MAX_BUFFERS; ++i) + if (ted->buffers_used[i]) + session_write_buffer(ted, fp, i); +} + +static void session_read_file(Ted *ted, FILE *fp) { + char version[sizeof SESSION_VERSION] = {0}; + fread(version, 1, sizeof version, fp); + if (memcmp(version, SESSION_VERSION, sizeof version) != 0) { + debug_println("WARNING: Session file has wrong version (see %s:%d)!\n", __FILE__, __LINE__); + return; // wrong version + } + + u16 active_node_idx = clamp_u16(read_u16(fp), 0, TED_MAX_NODES); + u16 active_buffer_idx = clamp_u16(read_u16(fp), 0, TED_MAX_BUFFERS); + + u16 nnodes = clamp_u16(read_u16(fp), 0, TED_MAX_NODES); + for (u16 i = 0; i < nnodes; ++i) { + session_read_node(ted, fp); + } + + u16 nbuffers = clamp_u16(read_u16(fp), 0, TED_MAX_BUFFERS); + for (u16 i = 0; i < nbuffers; ++i) { + session_read_buffer(ted, fp); + } + + if (ted->nodes_used[active_node_idx]) + ted->active_node = &ted->nodes[active_node_idx]; + if (ted->buffers_used[active_buffer_idx]) + ted->active_buffer = &ted->buffers[active_buffer_idx]; +} + +static void session_write(Ted *ted) { + Settings const *settings = &ted->settings; + if (!settings->restore_session) + return; + // first we write to a prefixed file so in case something goes wrong we still have the old session. + char filename1[TED_PATH_MAX], filename2[TED_PATH_MAX]; + strbuf_printf(filename1, "%s/_" SESSION_FILENAME, ted->local_data_dir); + strbuf_printf(filename2, "%s/" SESSION_FILENAME, ted->local_data_dir); + FILE *fp = fopen(filename1, "wb"); + if (fp) { + session_write_file(ted, fp); + + bool success = !ferror(fp); + success &= fclose(fp) == 0; + if (success) + rename(filename1, filename2); // overwrite old session + } +} + +static void session_read(Ted *ted) { + Settings const *settings = &ted->settings; + if (settings->restore_session) { + char filename[TED_PATH_MAX]; + strbuf_printf(filename, "%s/" SESSION_FILENAME, ted->local_data_dir); + FILE *fp = fopen(filename, "rb"); + if (fp) { + session_read_file(ted, fp); + fclose(fp); + } + } +} @@ -46,6 +46,10 @@ static void *ted_realloc(Ted *ted, void *p, size_t new_size) { return ret; } +static void ted_full_path(Ted *ted, char const *relpath, char *abspath, size_t abspath_size) { + path_full(ted->cwd, relpath, abspath, abspath_size); +} + // Check the various places a file could be, and return the full path. static Status ted_get_file(Ted const *ted, char const *name, char *out, size_t outsz) { if (ted->search_cwd && fs_file_exists(name)) { @@ -212,12 +216,15 @@ static Status ted_open_buffer(Ted *ted, u16 *buffer_idx, u16 *tab) { // Returns true on success static bool ted_open_file(Ted *ted, char const *filename) { + char path[TED_PATH_MAX]; + ted_full_path(ted, filename, path, sizeof path); + // first, check if file is already open bool *buffers_used = ted->buffers_used; TextBuffer *buffers = ted->buffers; for (u16 i = 0; i < TED_MAX_BUFFERS; ++i) { if (buffers_used[i]) { - if (streq(filename, buffer_get_filename(&buffers[i]))) { + if (buffers[i].filename && streq(path, buffers[i].filename)) { buffer_reload(&buffers[i]); // make sure buffer is up to date with the file ted_switch_to_buffer(ted, &buffers[i]); return true; @@ -229,10 +236,10 @@ static bool ted_open_file(Ted *ted, char const *filename) { u16 buffer_idx, tab_idx; if (ted->active_buffer && buffer_is_untitled(ted->active_buffer) && buffer_empty(ted->active_buffer)) { // the active buffer is just an empty untitled buffer. open it here. - return buffer_load_file(ted->active_buffer, filename); + return buffer_load_file(ted->active_buffer, path); } else if (ted_open_buffer(ted, &buffer_idx, &tab_idx)) { TextBuffer *buffer = &ted->buffers[buffer_idx]; - if (buffer_load_file(buffer, filename)) { + if (buffer_load_file(buffer, path)) { return true; } else { ted_seterr_to_buferr(ted, buffer); @@ -245,8 +252,13 @@ static bool ted_open_file(Ted *ted, char const *filename) { } } -static bool ted_new_file(Ted *ted) { +static bool ted_new_file(Ted *ted, char const *filename) { u16 buffer_idx, tab_idx; + char path[TED_PATH_MAX]; + if (filename) + ted_full_path(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, TED_UNTITLED); @@ -287,7 +299,3 @@ static bool ted_save_all(Ted *ted) { } return success; } - -static void ted_full_path(Ted *ted, char const *relpath, char *abspath, size_t abspath_size) { - path_full(ted->cwd, relpath, abspath, abspath_size); -} @@ -26,6 +26,8 @@ auto-reload = off build-default-command = make # file name for ctags output tags-filename = tags +# restore previously opened files when ted is launched? +restore-session = on [keyboard] # motion and selection @@ -72,6 +72,7 @@ typedef struct { bool syntax_highlighting; bool line_numbers; bool auto_reload; + bool restore_session; u8 tab_width; u8 cursor_width; u8 undo_save_time; @@ -203,7 +204,6 @@ typedef struct { // a node is a collection of tabs OR a split of two nodes typedef struct Node { u16 *tabs; // dynamic array of indices into ted->buffers, or NULL if this is a split - v2 size; // size of node, as a percentage of the window dimensions float split_pos; // number from 0 to 1 indicating where the split is. u16 active_tab; // index of active tab in tabs. bool split_vertical; // is the split vertical? if false, this split looks like a|b @@ -260,6 +260,10 @@ static bool path_is_absolute(char const *path) { // assuming `dir` is an absolute path, returns the absolute path of `relpath`, relative to `dir`. static void path_full(char const *dir, char const *relpath, char *abspath, size_t abspath_size) { + if (path_is_absolute(relpath)) { + str_cpy(abspath, abspath_size, relpath); + return; + } str_cpy(abspath, abspath_size, dir); while (1) { |