diff options
-rw-r--r-- | buffer.c | 140 | ||||
-rw-r--r-- | config.c | 6 | ||||
-rw-r--r-- | main.c | 6 | ||||
-rw-r--r-- | make.bat | 2 | ||||
-rw-r--r-- | math.c | 9 | ||||
-rw-r--r-- | menu.c | 244 | ||||
-rw-r--r-- | ted-base.c | 14 | ||||
-rw-r--r-- | ted.cfg | 1 | ||||
-rw-r--r-- | ted.h | 15 |
9 files changed, 286 insertions, 151 deletions
@@ -413,18 +413,18 @@ static Status buffer_line_set_min_capacity(TextBuffer *buffer, Line *line, u32 m return true; } -// grow capacity of buffer->lines array +// grow capacity of lines array // returns true if successful -static Status buffer_lines_set_min_capacity(TextBuffer *buffer, u32 minimum_capacity) { - while (minimum_capacity >= buffer->lines_capacity) { +static Status buffer_lines_set_min_capacity(TextBuffer *buffer, Line **lines, u32 *lines_capacity, u32 minimum_capacity) { + while (minimum_capacity >= *lines_capacity) { // allocate more lines - u32 new_capacity = buffer->lines_capacity * 2; - Line *new_lines = buffer_realloc(buffer, buffer->lines, new_capacity * sizeof *buffer->lines); + u32 new_capacity = *lines_capacity * 2; + Line *new_lines = buffer_realloc(buffer, *lines, new_capacity * sizeof(Line)); if (new_lines) { - buffer->lines = new_lines; - buffer->lines_capacity = new_capacity; + *lines = new_lines; // zero new lines - memset(buffer->lines + buffer->nlines, 0, (new_capacity - buffer->nlines) * sizeof *buffer->lines); + memset(new_lines + *lines_capacity, 0, (new_capacity - *lines_capacity) * sizeof(Line)); + *lines_capacity = new_capacity; } else { return false; } @@ -477,81 +477,97 @@ void buffer_clear(TextBuffer *buffer) { memcpy(buffer->error, error, sizeof error); } -void buffer_load_file(TextBuffer *buffer, char const *filename) { - buffer_clear(buffer); - - buffer->filename = buffer_strdup(buffer, filename); +// if an error occurs, buffer is left untouched (except for the error field) and the function returns false. +Status buffer_load_file(TextBuffer *buffer, char const *filename) { FILE *fp = fopen(filename, "rb"); + bool success = true; if (fp) { fseek(fp, 0, SEEK_END); size_t file_size = (size_t)ftell(fp); fseek(fp, 0, SEEK_SET); if (file_size > 10L<<20) { buffer_seterr(buffer, "File too big (size: %zu).", file_size); - return; - } - - u8 *file_contents = buffer_calloc(buffer, 1, file_size); - bool success = true; - if (file_contents) { - buffer->lines_capacity = 4; - buffer->lines = buffer_calloc(buffer, buffer->lines_capacity, sizeof *buffer->lines); // initial lines - if (buffer->lines) { - buffer->nlines = 1; - size_t bytes_read = fread(file_contents, 1, file_size, fp); - if (bytes_read == file_size) { - char32_t c = 0; - mbstate_t mbstate = {0}; - for (u8 *p = file_contents, *end = p + file_size; p != end; ) { - if (*p == '\r' && p != end-1 && p[1] == '\n') { - // CRLF line endings - p += 2; - c = U'\n'; - } else { - size_t n = mbrtoc32(&c, (char *)p, (size_t)(end - p), &mbstate); - if (n == 0) { - // null character - c = 0; - ++p; - } else if (n == (size_t)(-3)) { - // no bytes consumed, but a character was produced - } else if (n == (size_t)(-2) || n == (size_t)(-1)) { - // incomplete character at end of file or invalid UTF-8 respectively; fail - success = false; - buffer_seterr(buffer, "Invalid UTF-8 (position: %td).", p - file_contents); - break; + success = false; + } else { + u8 *file_contents = buffer_calloc(buffer, 1, file_size); + if (file_contents) { + u32 lines_capacity = 4; + Line *lines = buffer_calloc(buffer, lines_capacity, sizeof *buffer->lines); // initial lines + if (lines) { + u32 nlines = 1; + size_t bytes_read = fread(file_contents, 1, file_size, fp); + if (bytes_read == file_size) { + char32_t c = 0; + mbstate_t mbstate = {0}; + for (u8 *p = file_contents, *end = p + file_size; p != end; ) { + if (*p == '\r' && p != end-1 && p[1] == '\n') { + // CRLF line endings + p += 2; + c = U'\n'; + } else { + size_t n = mbrtoc32(&c, (char *)p, (size_t)(end - p), &mbstate); + if (n == 0) { + // null character + c = 0; + ++p; + } else if (n == (size_t)(-3)) { + // no bytes consumed, but a character was produced + } else if (n == (size_t)(-2) || n == (size_t)(-1)) { + // incomplete character at end of file or invalid UTF-8 respectively; fail + success = false; + buffer_seterr(buffer, "Invalid UTF-8 (position: %td).", p - file_contents); + break; + } else { + p += n; + } + } + if (c == U'\n') { + if (buffer_lines_set_min_capacity(buffer, &lines, &lines_capacity, nlines + 1)) + ++nlines; } else { - p += n; + u32 line_idx = nlines - 1; + Line *line = &lines[line_idx]; + buffer_line_append_char(buffer, line, c); } } - if (c == U'\n') { - if (buffer_lines_set_min_capacity(buffer, buffer->nlines + 1)) - ++buffer->nlines; - } else { - u32 line_idx = buffer->nlines - 1; - Line *line = &buffer->lines[line_idx]; - - buffer_line_append_char(buffer, line, c); + if (success) { + char *filename_copy = buffer_strdup(buffer, filename); + if (!filename_copy) success = false; + if (success) { + // everything is good + buffer_clear(buffer); + buffer->lines = lines; + buffer->nlines = nlines; + buffer->lines_capacity = lines_capacity; + buffer->filename = filename_copy; + } } + } else { + success = false; + } + if (!success) { + // something went wrong; we need to free all the memory we used + for (u32 i = 0; i < nlines; ++i) + buffer_line_free(&lines[i]); + free(lines); } } + free(file_contents); + } + if (ferror(fp)) { + buffer_seterr(buffer, "Error reading from file."); + success = false; } - free(file_contents); - } - if (ferror(fp)) { - buffer_seterr(buffer, "Error reading from file."); - success = false; } if (fclose(fp) != 0) { buffer_seterr(buffer, "Error closing file."); success = false; } - if (!success) { - buffer_clear(buffer); - } } else { buffer_seterr(buffer, "File %s does not exist.", filename); + success = false; } + return success; } void buffer_new_file(TextBuffer *buffer, char const *filename) { @@ -1122,7 +1138,7 @@ static Status buffer_insert_lines(TextBuffer *buffer, u32 where, u32 number) { u32 old_nlines = buffer->nlines; u32 new_nlines = old_nlines + number; - if (buffer_lines_set_min_capacity(buffer, new_nlines)) { + if (buffer_lines_set_min_capacity(buffer, &buffer->lines, &buffer->lines_capacity, new_nlines)) { assert(where <= old_nlines); // make space for new lines memmove(buffer->lines + where + (new_nlines - old_nlines), @@ -300,6 +300,12 @@ void config_read(Ted *ted, char const *filename) { } else { config_err(cfg, "Invalid border thickness: %s.", value); } + } else if (streq(key, "max-menu-width")) { + if (is_integer && integer >= 10 && integer < U16_MAX) { + settings->max_menu_width = (u16)integer; + } else { + config_err(cfg, "Invalid max menu width: %s.", value); + } } else { config_err(cfg, "Unrecognized core setting: %s.", key); } @@ -60,6 +60,7 @@ static void die(char const *fmt, ...) { #if _WIN32 INT WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, INT nCmdShow) { + (void)hInstance; (void)hPrevInstance; (void)lpCmdLine; (void)nCmdShow; int argc = 0; LPWSTR* wide_argv = CommandLineToArgvW(GetCommandLineW(), &argc); char** argv = malloc(argc * sizeof *argv); @@ -204,8 +205,7 @@ int main(int argc, char **argv) { } if (fs_file_exists(starting_filename)) { - buffer_load_file(buffer, starting_filename); - if (buffer_haserr(buffer)) + if (!buffer_load_file(buffer, starting_filename)) die("Error loading file: %s", buffer_geterr(buffer)); } else { buffer_new_file(buffer, starting_filename); @@ -346,6 +346,8 @@ int main(int argc, char **argv) { die("%s", ted_geterr(ted)); } } + + menu_update(ted, ted->menu); u32 key_modifier = (u32)ctrl_down << KEY_MODIFIER_CTRL_BIT | (u32)shift_down << KEY_MODIFIER_SHIFT_BIT @@ -4,7 +4,7 @@ if _%VCVARS% == _ ( call vcvarsall x64 ) -SET CFLAGS=/nologo /W3 /D_CRT_SECURE_NO_WARNINGS /I SDL2/include SDL2/lib/x64/SDL2main.lib SDL2/lib/x64/SDL2.lib opengl32.lib shell32.lib ole32.lib +SET CFLAGS=/nologo /W4 /wd4200 /wd4204 /wd4221 /wd4706 /D_CRT_SECURE_NO_WARNINGS /I SDL2/include SDL2/lib/x64/SDL2main.lib SDL2/lib/x64/SDL2.lib opengl32.lib shell32.lib ole32.lib rc /nologo ted.rc SET SOURCES=main.c text.c ted.res if _%1 == _ ( @@ -738,5 +738,14 @@ static float rects_intersect(Rect r1, Rect r2) { if (r2.pos.y >= r1.pos.y + r1.size.y) return false; // r2 is above r1 return true; } + +// returns whether or not there is any of the clipped rectangle left +static bool rect_clip_to_rect(Rect *clipped, Rect clipper) { + clipped->pos.x = maxf(clipped->pos.x, clipper.pos.x); + clipped->pos.y = maxf(clipped->pos.y, clipper.pos.y); + clipped->size.x = clampf(clipped->size.x, 0, clipper.pos.x + clipper.size.x - clipped->pos.x); + clipped->size.y = clampf(clipped->size.y, 0, clipper.pos.y + clipper.size.y - clipped->pos.y); + return clipped->size.x > 0 && clipped->size.y > 0; +} #endif @@ -19,6 +19,169 @@ static void menu_close(Ted *ted, bool restore_prev_active_buffer) { buffer_clear(&ted->line_buffer); } +// returns the rectangle of the screen coordinates of the menu +static Rect menu_rect(Ted *ted) { + Settings *settings = &ted->settings; + float window_width = ted->window_width, window_height = ted->window_height; + float padding = settings->padding; + float menu_width = settings->max_menu_width; + menu_width = minf(menu_width, window_width - 2 * padding); + return rect( + V2(window_width * 0.5f - 0.5f * menu_width, padding), + V2(menu_width, window_height - 2 * padding) + ); +} + +// where is the ith entry in the file selector on the screen? +// returns false if it's completely offscreen +static bool file_selector_entry_pos(Ted const *ted, FileSelector const *fs, + u32 i, Rect *r) { + Rect bounds = fs->bounds; + float char_height = text_font_char_height(ted->font); + *r = rect(V2(bounds.pos.x, bounds.pos.y + char_height * (float)i), + V2(bounds.size.x, char_height)); + return rect_clip_to_rect(r, bounds); +} + +// clear the entries in the file selector +static void file_selector_clear_entries(FileSelector *fs) { + for (u32 i = 0; i < fs->n_entries; ++i) { + free(fs->entries[i].name); + } + free(fs->entries); + fs->entries = NULL; + fs->n_entries = 0; +} + +static void file_selector_free(FileSelector *fs) { + file_selector_clear_entries(fs); +} + +// returns the entry of the selected file, or a NULL entry (check .name == NULL) +// if none was selected +static FileEntry file_selector_update(Ted *ted, FileSelector *fs, String32 const search_term32) { + char *search_term = search_term32.len ? str32_to_utf8_cstr(search_term32) : NULL; + + // check if an entry was clicked on + for (u32 i = 0; i < fs->n_entries; ++i) { + Rect r = {0}; + if (file_selector_entry_pos(ted, fs, i, &r)) { + for (u32 c = 0; c < ted->nmouse_clicks[SDL_BUTTON_LEFT]; ++c) { + if (rect_contains_point(r, ted->mouse_clicks[SDL_BUTTON_LEFT][c])) { + // this option was selected + free(search_term); + return fs->entries[i]; + } + } + } else break; + } + + // free previous entries + file_selector_clear_entries(fs); + // get new entries + char **files = fs_list_directory("."); + if (files) { + u32 nfiles; + for (nfiles = 0; files[nfiles]; ++nfiles); + if (search_term && *search_term) { + // filter entries based on search term + u32 in, out = 0; + for (in = 0; in < nfiles; ++in) { + if (stristr(files[in], search_term)) { + free(files[out]); + files[out++] = files[in]; + } + } + nfiles = out; + } + + if (nfiles) { + FileEntry *entries = ted_calloc(ted, nfiles, sizeof *entries); + if (entries) { + fs->n_entries = nfiles; + fs->entries = entries; + for (u32 i = 0; i < nfiles; ++i) { + entries[i].name = files[i]; + entries[i].type = fs_path_type(files[i]); + } + } + } + } else { + #if DEBUG + static bool have_warned; + if (!have_warned) { + debug_println("Warning: fs_list_directory failed."); + have_warned = true; + } + #endif + } + + free(search_term); + FileEntry ret = {0}; + return ret; +} + +static void file_selector_render(Ted const *ted, FileSelector const *fs) { + Settings const *settings = &ted->settings; + u32 const *colors = settings->colors; + Rect bounds = fs->bounds; + u32 n_entries = fs->n_entries; + FileEntry const *entries = fs->entries; + Font *font = ted->font; + float char_height = text_font_char_height(ted->font); + float x1, y1, x2, y2; + rect_coords(bounds, &x1, &y1, &x2, &y2); + float x = x1, y = y1; + for (u32 i = 0; i < n_entries; ++i) { + // highlight entry user is mousing over + if (y >= y2) break; + Rect r = rect4(x, y, x2, minf(y + char_height, y2)); + y += char_height; + if (rect_contains_point(r, ted->mouse_pos)) { + glBegin(GL_QUADS); + gl_color_rgba(colors[COLOR_MENU_HL]); + rect_render(r); + glEnd(); + } + } + + x = x1; y = y1; + TextRenderState text_render_state = {.min_x = x1, .max_x = x2, .min_y = y1, .max_y = y2, .render = true}; + // render file names themselves + for (u32 i = 0; i < n_entries; ++i) { + if (y >= y2) break; + switch (entries[i].type) { + case FS_FILE: + gl_color_rgba(colors[COLOR_TEXT]); + break; + case FS_DIRECTORY: + gl_color_rgba(colors[COLOR_TEXT_FOLDER]); + default: + gl_color_rgba(colors[COLOR_TEXT_OTHER]); + break; + } + text_render_with_state(font, &text_render_state, entries[i].name, x, y); + y += char_height; + } +} + +static void menu_update(Ted *ted, Menu menu) { + switch (menu) { + case MENU_NONE: break; + case MENU_OPEN: { + FileEntry selected_entry = file_selector_update(ted, &ted->file_selector, + buffer_get_line(&ted->line_buffer, 0)); + if (selected_entry.name) { + // open that file! + if (ted_open_file(ted, selected_entry.name)) { + menu_close(ted, false); + file_selector_free(&ted->file_selector); + } + } + } break; + } +} + static void menu_render(Ted *ted, Menu menu) { Settings *settings = &ted->settings; u32 *colors = settings->colors; @@ -34,8 +197,6 @@ static void menu_render(Ted *ted, Menu menu) { if (menu == MENU_OPEN) { - char *search_term = str32_to_utf8_cstr(buffer_get_line(&ted->line_buffer, 0)); - char const *directory = "."; float padding = 20; float menu_x1 = window_width * 0.5f - 300; float menu_x2 = window_width * 0.5f + 300; @@ -67,81 +228,8 @@ static void menu_render(Ted *ted, Menu menu) { buffer_render(&ted->line_buffer, line_buffer_x1, line_buffer_y1, line_buffer_x2, line_buffer_y2); - char **entries = fs_list_directory(directory); - u32 nentries = 0; - if (entries) { - for (char **p = entries; *p; ++p) - ++nentries; - } - FsType *entry_types = calloc(nentries, sizeof *entry_types); - if (entries && (entry_types || !nentries)) { - if (search_term && *search_term) { - // filter entries based on search term - u32 in, out = 0; - for (in = 0; in < nentries; ++in) { - if (stristr(entries[in], search_term)) { - entries[out++] = entries[in]; - } - } - nentries = out; - } - for (u32 i = 0; i < nentries; ++i) { - entry_types[i] = fs_path_type(entries[i]); - } - - qsort(entries, nentries, sizeof *entries, str_qsort_case_insensitive_cmp); - char const *file_to_open = NULL; - { // render file names - float start_x = menu_x1, start_y = line_buffer_y2 + inner_padding; - float x = start_x, y = start_y; - - for (u32 i = 0; i < nentries; ++i) { - // highlight entry user is mousing over - if (y >= menu_y2) break; - Rect r = rect4(x, y, menu_x2, minf(y + char_height, menu_y2)); - y += char_height; - if (rect_contains_point(r, ted->mouse_pos)) { - glBegin(GL_QUADS); - gl_color_rgba(colors[COLOR_MENU_HL]); - rect_render(r); - glEnd(); - } - for (u32 c = 0; c < ted->nmouse_clicks[SDL_BUTTON_LEFT]; ++c) { - if (rect_contains_point(r, ted->mouse_clicks[SDL_BUTTON_LEFT][c])) { - // this file got clicked on! - file_to_open = entries[i]; - } - } - } - x = start_x, y = start_y; - TextRenderState text_render_state = {.min_x = menu_x1, .max_x = menu_x2, .min_y = menu_y1, .max_y = menu_y2, .render = true}; - // render file names themselves - for (u32 i = 0; i < nentries; ++i) { - if (y >= menu_y2) break; - switch (entry_types[i]) { - case FS_FILE: - gl_color_rgba(colors[COLOR_TEXT]); - break; - case FS_DIRECTORY: - gl_color_rgba(colors[COLOR_TEXT_FOLDER]); - default: - gl_color_rgba(colors[COLOR_TEXT_OTHER]); - break; - } - text_render_with_state(font, &text_render_state, entries[i], x, y); - y += char_height; - } - } - - if (file_to_open) { - ted_open_file(ted, file_to_open); - menu_close(ted, false); - } - - for (u32 i = 0; i < nentries; ++i) free(entries[i]); - } - free(entry_types); - free(entries); - free(search_term); + FileSelector *fs = &ted->file_selector; + fs->bounds = rect4(menu_x1, line_buffer_y2, menu_x2, menu_y2); + file_selector_render(ted, fs); } } @@ -86,17 +86,15 @@ static void ted_load_font(Ted *ted) { } } -// returns buffer of new file -static TextBuffer *ted_open_file(Ted *ted, char const *filename) { +// returns buffer of new file, or NULL on failure +static WarnUnusedResult TextBuffer *ted_open_file(Ted *ted, char const *filename) { TextBuffer *open_to = &ted->main_buffer; - buffer_load_file(open_to, filename); - if (buffer_haserr(open_to)) { - // @TODO: something - ted_seterr_to_buferr(ted, open_to); - return NULL; - } else { + if (buffer_load_file(open_to, filename)) { ted->active_buffer = open_to; return open_to; + } else { + ted_seterr_to_buferr(ted, open_to); + return NULL; } } @@ -11,6 +11,7 @@ cursor-blink-time-off = 0.3 undo-save-time = 6 text-size = 16 border-thickness = 1 +max-menu-width = 600 [keyboard] # motion and selection @@ -7,10 +7,12 @@ typedef struct { float cursor_blink_time_on, cursor_blink_time_off; u32 colors[COLOR_COUNT]; u16 text_size; + u16 max_menu_width; u8 tab_width; u8 cursor_width; u8 undo_save_time; u8 border_thickness; + u8 padding; } Settings; #define SCANCODE_COUNT 0x120 // SDL scancodes should be less than this value. @@ -74,6 +76,18 @@ ENUM_U16 { MENU_OPEN } ENUM_U16_END(Menu); +// file entries for menus involving a file selector +typedef struct { + char *name; + FsType type; +} FileEntry; + +typedef struct { + Rect bounds; + u32 n_entries; + FileEntry *entries; +} FileSelector; + typedef struct Ted { Font *font; TextBuffer *active_buffer; @@ -86,6 +100,7 @@ typedef struct Ted { u8 nmouse_clicks[4]; // nmouse_clicks[i] = length of mouse_clicks[i] v2 mouse_clicks[4][32]; // mouse_clicks[SDL_BUTTON_RIGHT], for example, is all the right mouse-clicks that have happened this frame Menu menu; + FileSelector file_selector; TextBuffer line_buffer; // general-purpose line buffer for inputs -- used for menus TextBuffer main_buffer; KeyAction key_actions[KEY_COMBO_COUNT]; |