From bf906b2d5e1ddc5d77c06197d1105246e77b101a Mon Sep 17 00:00:00 2001 From: Leo Tenenbaum Date: Tue, 22 Dec 2020 17:24:03 -0500 Subject: undo actually not quite done yet --- base.h | 10 ++++++ buffer.c | 108 +++++++++++++++++++++++++++++++++++++++++---------------------- main.c | 5 +++ time.c | 65 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 151 insertions(+), 37 deletions(-) create mode 100644 time.c diff --git a/base.h b/base.h index b17a998..b4a9b00 100644 --- a/base.h +++ b/base.h @@ -24,16 +24,26 @@ typedef uint16_t u16; typedef uint32_t u32; typedef uint64_t u64; +// (for u8 and u16, you can use %u) #define U32_FMT "%" PRIu32 #define U64_FMT "%" PRIu64 +#define U8_MAX 0xff +#define U16_MAX 0xffff +#define U32_MAX 0xffffffff +#define U64_MAX 0xffffffffffffffff typedef int8_t i8; typedef int16_t i16; typedef int32_t i32; typedef int64_t i64; +// (for i8 and i16, you can use %d) #define I32_FMT "%" PRId32 #define I64_FMT "%" PRId64 +#define I8_MAX 0x7f +#define I16_MAX 0x7fff +#define I32_MAX 0x7fffffff +#define I64_MAX 0x7fffffffffffffff typedef unsigned int uint; typedef unsigned long ulong; diff --git a/buffer.c b/buffer.c index 8b1c097..a3426e7 100644 --- a/buffer.c +++ b/buffer.c @@ -1,10 +1,4 @@ // Text buffers - These store the contents of a file. -#include -#include "unicode.h" -#include "util.c" -#include "text.h" -#include "string32.c" -#include "arr.c" // a position in the buffer typedef struct { @@ -21,9 +15,10 @@ typedef struct { // this refers to replacing prev_len characters (found in prev_text) at pos with new_len characters typedef struct BufferEdit { BufferPos pos; - size_t new_len; - size_t prev_len; + u32 new_len; + u32 prev_len; char32_t *prev_text; + struct timespec time; } BufferEdit; typedef struct { @@ -167,10 +162,13 @@ BufferPos buffer_end_pos(TextBuffer *buffer) { } -// get some number of characters of text from the given position in the buffer. -static Status buffer_get_text_at_pos(TextBuffer *buffer, BufferPos pos, char32_t *text, size_t nchars) { +// Get some number of characters of text from the given position in the buffer. +// Returns the number of characters gotten. +// You can pass NULL for text if you just want to know how many characters *could* be accessed before the +// end of the file. +static size_t buffer_get_text_at_pos(TextBuffer *buffer, BufferPos pos, char32_t *text, size_t nchars) { if (!buffer_pos_valid(buffer, pos)) { - return false; + return 0; // invalid position. no chars for you! } char32_t *p = text; size_t chars_left = nchars; @@ -179,24 +177,25 @@ static Status buffer_get_text_at_pos(TextBuffer *buffer, BufferPos pos, char32_t while (chars_left) { u32 chars_from_this_line = line->len - index; if (chars_left <= chars_from_this_line) { - memcpy(p, line->str, chars_left * sizeof *p); + if (p) memcpy(p, line->str, chars_left * sizeof *p); chars_left = 0; } else { - memcpy(p, line->str, chars_from_this_line * sizeof *p); - p += chars_from_this_line; - *p++ = U'\n'; + if (p) { + memcpy(p, line->str, chars_from_this_line * sizeof *p); + p += chars_from_this_line; + *p++ = U'\n'; + } chars_left -= chars_from_this_line+1; } index = 0; ++line; if (chars_left && line == end) { - // reached end of file before getting full text... this isn't good - buffer_seterr(buffer, "Failed to fetch %zu characters at " U32_FMT ":" U32_FMT ".", nchars, pos.line+1, pos.index+1); - return false; + // reached end of file before getting full text + break; } } - return true; + return nchars - chars_left; } static BufferPos buffer_pos_advance(TextBuffer *buffer, BufferPos pos, size_t nchars) { @@ -250,7 +249,7 @@ static i64 buffer_pos_diff(TextBuffer *buffer, BufferPos p1, BufferPos p2) { return total * factor; } -static Status buffer_create_edit(TextBuffer *buffer, BufferEdit *edit, BufferPos start, size_t prev_len, size_t new_len) { +static Status buffer_create_edit(TextBuffer *buffer, BufferEdit *edit, BufferPos start, u32 prev_len, u32 new_len) { if (prev_len == 0) edit->prev_text = NULL; // if there's no previous text, don't allocate anything else @@ -260,10 +259,8 @@ static Status buffer_create_edit(TextBuffer *buffer, BufferEdit *edit, BufferPos edit->prev_len = prev_len; edit->new_len = new_len; if (prev_len) { - if (!buffer_get_text_at_pos(buffer, start, edit->prev_text, prev_len)) { - buffer_edit_free(edit); - return false; - } + size_t chars_gotten = buffer_get_text_at_pos(buffer, start, edit->prev_text, prev_len); + edit->prev_len = (u32)chars_gotten; // update the previous length, in case it went past the end of the file } return true; } else { @@ -273,7 +270,7 @@ static Status buffer_create_edit(TextBuffer *buffer, BufferEdit *edit, BufferPos // add this edit to the undo history // call this before actually changing buffer -static void buffer_edit(TextBuffer *buffer, BufferPos start, size_t prev_len, size_t new_len) { +static void buffer_edit(TextBuffer *buffer, BufferPos start, u32 prev_len, u32 new_len) { BufferEdit edit = {0}; if (buffer_create_edit(buffer, &edit, start, prev_len, new_len)) { buffer_append_edit(buffer, &edit); @@ -950,6 +947,12 @@ static void buffer_insert_lines(TextBuffer *buffer, u32 where, u32 number) { // inserts the given text, returning the position of the end of the text BufferPos buffer_insert_text_at_pos(TextBuffer *buffer, BufferPos pos, String32 str) { + if (str.len > U32_MAX) { + buffer_seterr(buffer, "Inserting too much text (length: %zu).", str.len); + BufferPos ret = {0,0}; + return ret; + } + if (buffer->store_undo_events) { BufferEdit *last_edit = buffer->undo_history; i64 where_in_last_edit = last_edit ? buffer_pos_diff(buffer, last_edit->pos, pos) : -1; @@ -958,7 +961,7 @@ BufferPos buffer_insert_text_at_pos(TextBuffer *buffer, BufferPos pos, String32 last_edit->new_len += str.len; } else { // create a new edit for this insertion - buffer_edit(buffer, pos, 0, str.len); + buffer_edit(buffer, pos, 0, (u32)str.len); } } @@ -1062,18 +1065,52 @@ static void buffer_delete_lines(TextBuffer *buffer, u32 first_line_idx, u32 nlin memmove(first_line, end, (size_t)(buffer->lines + buffer->nlines - end) * sizeof(Line)); } -void buffer_delete_chars_at_pos(TextBuffer *buffer, BufferPos pos, i64 nchars) { - assert(nchars >= 0); - if (nchars <= 0) return; +void buffer_delete_chars_at_pos(TextBuffer *buffer, BufferPos pos, i64 nchars_) { + if (nchars_ < 0) { + buffer_seterr(buffer, "Deleting negative characters (specifically, " I64_FMT ").", nchars_); + return; + } + if (nchars_ <= 0) return; + if (nchars_ > U32_MAX) nchars_ = U32_MAX; + u32 nchars = (u32)nchars_; + + // Correct nchars in case it goes past the end of the file. + // Why do we need to correct it? + // When generating undo events, we allocate the given number of characters, + // even if that's not necessary (also see the allocating of `temp` below). + nchars = (u32)buffer_get_text_at_pos(buffer, pos, NULL, nchars); if (buffer->store_undo_events) { BufferEdit *last_edit = buffer->undo_history; i64 where_in_last_edit = last_edit ? buffer_pos_diff(buffer, last_edit->pos, pos) : -1; if (where_in_last_edit >= 0 && where_in_last_edit <= (i64)last_edit->new_len) { + // merge this edit into the last one. + #if 0 + // ah this doesn't work + // some of the deleted text might be inside the edit. + // this is gonna be annoying + + char32_t *temp = calloc(nchars, sizeof *temp); + if (temp) { + buffer_get_text_at_pos(buffer, pos, temp, nchars); + char32_t *prev_text = buffer_realloc(buffer, last_edit->prev_text, + (last_edit->prev_len + nchars) * sizeof *prev_text); + if (prev_text) { + // make space for our text + memmove(prev_text + where_in_last_edit + nchars, prev_text + where_in_last_edit, + last_edit->prev_len - (where_in_last_edit + nchars) * sizeof *prev_text); + // insert our text into new_text + memcpy(prev_text + where_in_last_edit, temp, nchars * sizeof *prev_text); + last_edit->prev_len += nchars; + last_edit->prev_text = prev_text; + } + free(temp); + } + #endif } else { // create a new edit - buffer_edit(buffer, pos, (size_t)nchars, 0); + buffer_edit(buffer, pos, nchars, 0); } } @@ -1090,12 +1127,9 @@ void buffer_delete_chars_at_pos(TextBuffer *buffer, BufferPos pos, i64 nchars) { nchars -= last_line->len+1; } if (last_line == lines_end) { - // delete everything to the end of the file - for (u32 idx = line_idx + 1; idx < buffer->nlines; ++idx) { - buffer_line_free(&buffer->lines[idx]); - } - buffer_shorten(buffer, line_idx + 1); - } else { + assert(nchars == 0); // we already shortened nchars to go no further than the end of the file + } + { // join last_line to line. u32 last_line_chars_left = (u32)(last_line->len - nchars); if (buffer_line_set_min_capacity(buffer, line, line->len + last_line_chars_left)) { @@ -1111,7 +1145,7 @@ void buffer_delete_chars_at_pos(TextBuffer *buffer, BufferPos pos, i64 nchars) { } else { // just delete characters from this line memmove(line->str + index, line->str + index + nchars, (size_t)(line->len - (nchars + index)) * sizeof(char32_t)); - line->len -= (u32)nchars; + line->len -= nchars; } } diff --git a/main.c b/main.c index 0a7b420..ea2c16f 100644 --- a/main.c +++ b/main.c @@ -8,7 +8,12 @@ no_warn_start no_warn_end #include #include +#include +#include "unicode.h" +#include "util.c" #include "text.h" +#include "string32.c" +#include "arr.c" #include "buffer.c" static void die(char const *fmt, ...) { diff --git a/time.c b/time.c new file mode 100644 index 0000000..fb007ba --- /dev/null +++ b/time.c @@ -0,0 +1,65 @@ +#include +#include +#include + +static struct timespec time_last_modified(char const *filename) { +#if __unix__ + struct stat statbuf = {}; + stat(filename, &statbuf); + return statbuf.st_mtim; +#else + // windows' _stat does not have st_mtim + struct _stat statbuf = {}; + struct timespec ts = {}; + _stat(filename, &statbuf); + ts.tv_sec = statbuf.st_mtime; + return ts; +#endif +} + +static int timespec_cmp(struct timespec a, struct timespec b) { + if (a.tv_sec > b.tv_sec) return 1; + if (a.tv_sec < b.tv_sec) return -1; + if (a.tv_nsec > b.tv_nsec) return 1; + if (a.tv_nsec < b.tv_nsec) return -1; + return 0; +} + +static bool timespec_eq(struct timespec a, struct timespec b) { + return timespec_cmp(a, b) == 0; +} + +static struct timespec timespec_max(struct timespec a, struct timespec b) { + return timespec_cmp(a, b) < 0 ? b : a; +} + +// sleep for a certain number of nanoseconds +static void sleep_ns(u64 ns) { +#if __unix__ + struct timespec rem = {}, req = { + (time_t)(ns / 1000000000), + (long)(ns % 1000000000) + }; + + while (nanosleep(&req, &rem) == EINTR) // sleep interrupted by signal + req = rem; +#else + // windows.... + Sleep((DWORD)(ns / 1000000)); +#endif +} + +// sleep for microseconds +static void time_sleep_us(u64 us) { + sleep_ns(us * 1000); +} + +// sleep for milliseconds +static void time_sleep_ms(u64 ms) { + sleep_ns(ms * 1000000); +} + +// sleep for seconds +static void time_sleep_s(u64 s) { + sleep_ns(s * 1000000000); +} -- cgit v1.2.3