summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--buffer.c140
-rw-r--r--config.c6
-rw-r--r--main.c6
-rw-r--r--make.bat2
-rw-r--r--math.c9
-rw-r--r--menu.c244
-rw-r--r--ted-base.c14
-rw-r--r--ted.cfg1
-rw-r--r--ted.h15
9 files changed, 286 insertions, 151 deletions
diff --git a/buffer.c b/buffer.c
index 9481236..162547a 100644
--- a/buffer.c
+++ b/buffer.c
@@ -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),
diff --git a/config.c b/config.c
index 3bce64d..458b455 100644
--- a/config.c
+++ b/config.c
@@ -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);
}
diff --git a/main.c b/main.c
index bfbeac2..f014d89 100644
--- a/main.c
+++ b/main.c
@@ -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
diff --git a/make.bat b/make.bat
index b257d7d..0f4fcdf 100644
--- a/make.bat
+++ b/make.bat
@@ -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 == _ (
diff --git a/math.c b/math.c
index 8de9daf..c920a80 100644
--- a/math.c
+++ b/math.c
@@ -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
diff --git a/menu.c b/menu.c
index 334d17c..9c194c1 100644
--- a/menu.c
+++ b/menu.c
@@ -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);
}
}
diff --git a/ted-base.c b/ted-base.c
index 2eac197..c3aff0e 100644
--- a/ted-base.c
+++ b/ted-base.c
@@ -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;
}
}
diff --git a/ted.cfg b/ted.cfg
index 49ff2a4..c4cf97c 100644
--- a/ted.cfg
+++ b/ted.cfg
@@ -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
diff --git a/ted.h b/ted.h
index af1a2ae..8f2958f 100644
--- a/ted.h
+++ b/ted.h
@@ -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];