summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLeo Tenenbaum <pommicket@gmail.com>2021-03-01 15:02:40 -0500
committerLeo Tenenbaum <pommicket@gmail.com>2021-03-01 15:02:40 -0500
commit12bb6e2cd377d3dfa2f7ec037c31463c3f9070d1 (patch)
treee3311ad77bd0fb768adcdce7582397cf97f40183
parent2d327302b30bf67a87538dc5e005c0399c0b8f1a (diff)
restore session
-rw-r--r--buffer.c16
-rw-r--r--command.c5
-rw-r--r--config.c1
-rw-r--r--io.c135
-rw-r--r--main.c49
-rw-r--r--node.c2
-rw-r--r--session.c173
-rw-r--r--ted.c24
-rw-r--r--ted.cfg2
-rw-r--r--ted.h2
-rw-r--r--util.c4
11 files changed, 368 insertions, 45 deletions
diff --git a/buffer.c b/buffer.c
index a3d9c51..28ec364 100644
--- a/buffer.c
+++ b/buffer.c
@@ -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))
diff --git a/command.c b/command.c
index 91819e4..274d8c8 100644
--- a/command.c
+++ b/command.c
@@ -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;
}
diff --git a/config.c b/config.c
index 9eedbf1..721fbba 100644
--- a/config.c
+++ b/config.c
@@ -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},
diff --git a/io.c b/io.c
new file mode 100644
index 0000000..bb0ced7
--- /dev/null
+++ b/io.c
@@ -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;
+ }
+}
diff --git a/main.c b/main.c
index 10dcfd9..6524b40 100644
--- a/main.c
+++ b/main.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);
diff --git a/node.c b/node.c
index e6a2f97..7facab2 100644
--- a/node.c
+++ b/node.c
@@ -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);
+ }
+ }
+}
diff --git a/ted.c b/ted.c
index 5989f45..2c8fb92 100644
--- a/ted.c
+++ b/ted.c
@@ -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);
-}
diff --git a/ted.cfg b/ted.cfg
index aed1794..b149ac6 100644
--- a/ted.cfg
+++ b/ted.cfg
@@ -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
diff --git a/ted.h b/ted.h
index adf3587..8a6893d 100644
--- a/ted.h
+++ b/ted.h
@@ -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
diff --git a/util.c b/util.c
index 9ceb4cc..f99524d 100644
--- a/util.c
+++ b/util.c
@@ -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) {