summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--arr.c18
-rw-r--r--base.h6
-rw-r--r--buffer.c26
-rw-r--r--command.c9
-rw-r--r--filesystem-posix.c11
-rw-r--r--filesystem-win.c12
-rw-r--r--filesystem.h10
-rw-r--r--main.c47
-rw-r--r--menu.c173
-rw-r--r--ted-base.c4
-rw-r--r--ted.h10
-rw-r--r--ui.c234
12 files changed, 348 insertions, 212 deletions
diff --git a/arr.c b/arr.c
index 0fc856f..93f6198 100644
--- a/arr.c
+++ b/arr.c
@@ -109,8 +109,6 @@ static inline void *arr_add_ptr_(void **arr, size_t member_size) {
return ret;
}
-#if 0
-// NOT TESTED
static inline void arr_set_len_(void **arr, size_t member_size, size_t n) {
if (n >= U32_MAX-1) {
// too big; free arr.
@@ -153,10 +151,10 @@ static inline void arr_set_len_(void **arr, size_t member_size, size_t n) {
memset((char *)hdr->data + hdr->len, 0, (n - hdr->len) * member_size);
}
hdr->len = (u32)n;
+ *arr = hdr->data;
}
}
#define arr_set_len(a, n) arr_set_len_((void **)&a, sizeof *(a), n)
-#endif
#ifdef __cplusplus
#define arr_cast_typeof(a) (decltype(a))
@@ -209,6 +207,20 @@ static inline void arr_set_len_(void **arr, size_t member_size, size_t n) {
} \
} while (0)
+
+static void arr_append_str_(char **a, char const *s) {
+ size_t curr_len = arr_len(*a);
+ size_t s_len = strlen(s);
+
+ if (curr_len) --curr_len; // don't include null terminator
+
+ arr_set_len(*a, curr_len + s_len + 1);
+ memcpy(*a + curr_len, s, s_len + 1);
+}
+
+// appends a C-string array with
+#define arr_append_str(a, s) arr_append_str_(&(a), (s))
+
static void arr_test(void) {
u32 *arr = NULL;
u32 i;
diff --git a/base.h b/base.h
index 53e0a70..f6b0452 100644
--- a/base.h
+++ b/base.h
@@ -8,9 +8,11 @@
#if _WIN32
#include <windows.h>
#include <shlobj.h>
-#define PATH_SEPARATOR "\\"
+#define PATH_SEPARATOR '\\'
+#define PATH_SEPARATOR_STR "\\"
#else
-#define PATH_SEPARATOR "/"
+#define PATH_SEPARATOR '/'
+#define PATH_SEPARATOR_STR "/"
#endif
#include <stdbool.h>
diff --git a/buffer.c b/buffer.c
index 520d7d2..3663df4 100644
--- a/buffer.c
+++ b/buffer.c
@@ -768,8 +768,9 @@ v2 buffer_pos_to_pixels(TextBuffer *buffer, BufferPos pos) {
}
// convert pixel coordinates to a position in the buffer, selecting the closest character.
-// returns false if the position is not inside the buffer.
+// returns false if the position is not inside the buffer, but still sets *pos to the closest character.
bool buffer_pixels_to_pos(TextBuffer *buffer, v2 pixel_coords, BufferPos *pos) {
+ bool ret = true;
float x = pixel_coords.x, y = pixel_coords.y;
Font *font = buffer_font(buffer);
pos->line = pos->index = 0;
@@ -779,11 +780,24 @@ bool buffer_pixels_to_pos(TextBuffer *buffer, v2 pixel_coords, BufferPos *pos) {
x /= text_font_char_width(font);
y /= text_font_char_height(font);
double display_col = (double)x;
- if (display_col < 0 || display_col >= buffer_display_cols(buffer))
- return false;
+ if (display_col < 0) {
+ display_col = 0;
+ ret = false;
+ }
+ int display_cols = buffer_display_cols(buffer), display_lines = buffer_display_lines(buffer);
+ if (display_col >= display_cols) {
+ display_col = display_cols - 1;
+ ret = false;
+ }
double display_line = (double)y;
- if (display_line < 0 || display_line >= buffer_display_lines(buffer))
- return false;
+ if (display_line < 0) {
+ display_line = 0;
+ ret = false;
+ }
+ if (display_line >= display_lines) {
+ display_line = display_lines - 1;
+ ret = false;
+ }
u32 line = (u32)floor(display_line + buffer->scroll_y);
if (line >= buffer->nlines) line = buffer->nlines - 1;
@@ -792,7 +806,7 @@ bool buffer_pixels_to_pos(TextBuffer *buffer, v2 pixel_coords, BufferPos *pos) {
pos->line = line;
pos->index = index;
- return true;
+ return ret;
}
// clip the rectangle so it's all inside the buffer. returns true if there's any rectangle left.
diff --git a/command.c b/command.c
index 206274a..f5ceb0c 100644
--- a/command.c
+++ b/command.c
@@ -156,14 +156,7 @@ void command_execute(Ted *ted, Command c, i64 argument) {
assert(0);
break;
case MENU_OPEN: {
- char *filename_cstr = str32_to_utf8_cstr(buffer_get_line(&ted->line_buffer, 0));
- if (filename_cstr) {
- buffer = ted_open_file(ted, filename_cstr);
- free(filename_cstr);
- menu_close(ted, false);
- } else {
- ted_seterr(ted, "Out of memory.");
- }
+ ted->file_selector.submitted = true;
} break;
}
}
diff --git a/filesystem-posix.c b/filesystem-posix.c
index 1a23eff..ebf1407 100644
--- a/filesystem-posix.c
+++ b/filesystem-posix.c
@@ -69,3 +69,14 @@ int fs_mkdir(char const *path) {
return -1;
}
}
+
+int fs_get_cwd(char *buf, size_t buflen) {
+ assert(buf && buflen);
+ if (getcwd(buf, buflen)) {
+ return 1;
+ } else if (errno == ERANGE) {
+ return 0;
+ } else {
+ return -1;
+ }
+}
diff --git a/filesystem-win.c b/filesystem-win.c
index 7c40820..d35597f 100644
--- a/filesystem-win.c
+++ b/filesystem-win.c
@@ -65,3 +65,15 @@ int fs_mkdir(char const *path) {
return -1; // some other error
}
}
+
+int fs_get_cwd(char *buf, size_t buflen) {
+ assert(buf && buflen);
+ DWORD pathlen = GetCurrentDirectory(buflen, buf);
+ if (pathlen == 0) {
+ return -1;
+ } else if (pathlen < buflen) { // it's confusing, but this is < and not <=
+ return 1;
+ } else {
+ return 0;
+ }
+}
diff --git a/filesystem.h b/filesystem.h
index 238ebb1..2e561cd 100644
--- a/filesystem.h
+++ b/filesystem.h
@@ -16,12 +16,18 @@ bool fs_file_exists(char const *path);
// When you're done with the file names, call free on each one, then on the array.
// NOTE: The files aren't returned in any particular order!
char **fs_list_directory(char const *dirname);
-// create the directory specified by `path`
-// returns:
+// Create the directory specified by `path`
+// Returns:
// 1 if the directory was created successfully
// 0 if the directory already exists
// -1 if the path already exists, but it's not a directory, or if there's another error (e.g. don't have permission to create directory).
int fs_mkdir(char const *path);
+// Puts the current working directory into buf, including a null-terminator, writing at most buflen bytes.
+// Returns:
+// 1 if the working directory was inserted into buf successfully
+// 0 if buf is too short to hold the cwd
+// -1 if we can't get the cwd for whatever reason.
+int fs_get_cwd(char *buf, size_t buflen);
#endif // FILESYSTEM_H_
diff --git a/main.c b/main.c
index 39e582b..eddefa1 100644
--- a/main.c
+++ b/main.c
@@ -37,6 +37,7 @@ no_warn_end
#include "arr.c"
#include "buffer.c"
#include "ted-base.c"
+#include "ui.c"
#include "command.c"
#include "config.c"
#include "menu.c"
@@ -81,14 +82,17 @@ int main(int argc, char **argv) {
#endif
setlocale(LC_ALL, ""); // allow unicode
- printf("%d\n", !!stristr("foo", "x"));
+ Ted *ted = calloc(1, sizeof *ted);
+ if (!ted) {
+ die("Not enough memory available to run ted.");
+ }
{ // get local data directory
#if _WIN32
wchar_t *appdata = NULL;
KNOWNFOLDERID id = FOLDERID_LocalAppData;
if (SHGetKnownFolderPath(&id, 0, NULL, &appdata) == S_OK) {
- strbuf_printf(ted_local_data_dir, "%ls" PATH_SEPARATOR "ted", appdata);
+ strbuf_printf(ted_local_data_dir, "%ls" PATH_SEPARATOR_STR "ted", appdata);
CoTaskMemFree(appdata);
}
#else
@@ -97,12 +101,17 @@ int main(int argc, char **argv) {
#endif
}
+ { // get current working directory
+ fs_get_cwd(ted->cwd, sizeof ted->cwd);
+ }
+
{ // check if this is the installed version of ted (as opposed to just running it from the directory with the source)
- char executable_path[TED_PATH_MAX] = {0}, cwd[TED_PATH_MAX] = {0};
+ char executable_path[TED_PATH_MAX] = {0};
+ char const *cwd = ted->cwd;
#if _WIN32
if (GetModuleFileNameA(NULL, executable_path, sizeof executable_path) > 0) {
char *last_backslash = strrchr(executable_path, '\\');
- if (last_backslash && GetCurrentDirectory(sizeof cwd, cwd) > 0) {
+ if (last_backslash) {
*last_backslash = '\0';
ted_search_cwd = streq(cwd, executable_path);
}
@@ -116,26 +125,18 @@ int main(int argc, char **argv) {
char *last_slash = strrchr(executable_path, '/');
if (last_slash) {
*last_slash = '\0';
- if (getcwd(cwd, sizeof cwd)) {
- // @TODO: make sure this is working
- ted_search_cwd = streq(cwd, executable_path);
- }
+ ted_search_cwd = streq(cwd, executable_path);
}
}
#endif
}
- Ted *ted = calloc(1, sizeof *ted);
- if (!ted) {
- die("Not enough memory available to run ted.");
- }
-
Settings *settings = &ted->settings;
{
// read global configuration file first to establish defaults
char global_config_filename[TED_PATH_MAX];
- strbuf_printf(global_config_filename, "%s" PATH_SEPARATOR "ted.cfg", ted_global_data_dir);
+ strbuf_printf(global_config_filename, "%s" PATH_SEPARATOR_STR "ted.cfg", ted_global_data_dir);
if (fs_file_exists(global_config_filename))
config_read(ted, global_config_filename);
}
@@ -246,7 +247,7 @@ int main(int argc, char **argv) {
bool ctrl_down = keyboard_state[SDL_SCANCODE_LCTRL] || keyboard_state[SDL_SCANCODE_RCTRL];
bool shift_down = keyboard_state[SDL_SCANCODE_LSHIFT] || keyboard_state[SDL_SCANCODE_RSHIFT];
bool alt_down = keyboard_state[SDL_SCANCODE_LALT] || keyboard_state[SDL_SCANCODE_RALT];
-
+
memset(ted->nmouse_clicks, 0, sizeof ted->nmouse_clicks);
while (SDL_PollEvent(&event)) {
@@ -293,18 +294,26 @@ int main(int argc, char **argv) {
break;
}
}
+ ted->drag_buffer = buffer;
}
}
} break;
}
} break;
+ case SDL_MOUSEBUTTONUP:
+ if (event.button.button == SDL_BUTTON_LEFT)
+ ted->drag_buffer = NULL;
+ break;
case SDL_MOUSEMOTION:
if (event.motion.state == SDL_BUTTON_LMASK) {
- if (buffer) {
+ if (ted->drag_buffer != ted->active_buffer)
+ ted->drag_buffer = NULL;
+ if (ted->drag_buffer) {
BufferPos pos = {0};
- if (buffer_pixels_to_pos(buffer, V2((float)event.button.x, (float)event.button.y), &pos)) {
- buffer_select_to_pos(buffer, pos);
- }
+ // drag to select
+ // we don't check the return value here, because it's okay to drag off the screen.
+ buffer_pixels_to_pos(ted->drag_buffer, V2((float)event.button.x, (float)event.button.y), &pos);
+ buffer_select_to_pos(ted->drag_buffer, pos);
}
}
break;
diff --git a/menu.c b/menu.c
index 4d7e965..a4f9c11 100644
--- a/menu.c
+++ b/menu.c
@@ -32,172 +32,18 @@ static Rect menu_rect(Ted *ted) {
);
}
-// 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);
-}
-
-static int qsort_file_entry_cmp(void const *av, void const *bv) {
- FileEntry const *a = av, *b = bv;
- // put directories first
- if (a->type > b->type) {
- return -1;
- }
- if (a->type < b->type) {
- return +1;
- }
- return strcmp_case_insensitive(a->name, b->name);
-}
-
-// 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);
-
- // filter entries
- bool increment = true;
- for (u32 i = 0; i < nfiles; i += increment, increment = true) {
- // remove if the file name does not contain the search term,
- bool remove = search_term && *search_term && !stristr(files[i], search_term);
- // or if this is just the current directory
- remove |= streq(files[i], ".");
- if (remove) {
- // remove this one
- free(files[i]);
- --nfiles;
- if (nfiles) {
- files[i] = files[nfiles];
- }
- increment = false;
- }
- }
-
- 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]);
- }
- }
- qsort(entries, nfiles, sizeof *entries, qsort_file_entry_cmp);
- }
- } 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]);
- break;
- 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) {
+ char *selected_file = file_selector_update(ted, &ted->file_selector);
+ if (selected_file) {
// open that file!
- if (ted_open_file(ted, selected_entry.name)) {
+ if (ted_open_file(ted, selected_file)) {
menu_close(ted, false);
file_selector_free(&ted->file_selector);
}
+ free(selected_file);
}
} break;
}
@@ -206,9 +52,7 @@ static void menu_update(Ted *ted, Menu menu) {
static void menu_render(Ted *ted, Menu menu) {
Settings *settings = &ted->settings;
u32 *colors = settings->colors;
- Font *font = ted->font;
float window_width = ted->window_width, window_height = ted->window_height;
- float char_height = text_font_char_height(font);
// render backdrop
glBegin(GL_QUADS);
@@ -241,16 +85,9 @@ static void menu_render(Ted *ted, Menu menu) {
menu_x2 -= inner_padding;
menu_y2 -= inner_padding;
- float line_buffer_height = char_height * 1.5f;
- float line_buffer_x1 = menu_x1,
- line_buffer_y1 = menu_y1,
- line_buffer_x2 = menu_x2,
- line_buffer_y2 = line_buffer_y1 + line_buffer_height;
-
- buffer_render(&ted->line_buffer, line_buffer_x1, line_buffer_y1, line_buffer_x2, line_buffer_y2);
FileSelector *fs = &ted->file_selector;
- fs->bounds = rect4(menu_x1, line_buffer_y2, menu_x2, menu_y2);
+ fs->bounds = rect4(menu_x1, menu_y1, menu_x2, menu_y2);
file_selector_render(ted, fs);
}
}
diff --git a/ted-base.c b/ted-base.c
index c3aff0e..001cb94 100644
--- a/ted-base.c
+++ b/ted-base.c
@@ -56,12 +56,12 @@ static Status ted_get_file(char const *name, char *out, size_t outsz) {
return true;
}
if (*ted_local_data_dir) {
- str_printf(out, outsz, "%s" PATH_SEPARATOR "%s", ted_local_data_dir, name);
+ str_printf(out, outsz, "%s" PATH_SEPARATOR_STR "%s", ted_local_data_dir, name);
if (fs_file_exists(out))
return true;
}
if (*ted_global_data_dir) {
- str_printf(out, outsz, "%s" PATH_SEPARATOR "%s", ted_global_data_dir, name);
+ str_printf(out, outsz, "%s" PATH_SEPARATOR_STR "%s", ted_global_data_dir, name);
if (fs_file_exists(out))
return true;
}
diff --git a/ted.h b/ted.h
index 8f2958f..86f3269 100644
--- a/ted.h
+++ b/ted.h
@@ -76,9 +76,10 @@ ENUM_U16 {
MENU_OPEN
} ENUM_U16_END(Menu);
-// file entries for menus involving a file selector
+// file entries for file selectors
typedef struct {
- char *name;
+ char *name; // just the file name
+ char *path; // full path
FsType type;
} FileEntry;
@@ -86,11 +87,15 @@ typedef struct {
Rect bounds;
u32 n_entries;
FileEntry *entries;
+ char *cwd; // a dynamic null-terminated array of chars representing the current directory
+ bool submitted; // set to true if the line buffer was just submitted this frame.
} FileSelector;
typedef struct Ted {
Font *font;
TextBuffer *active_buffer;
+ // buffer we are currently drag-to-selecting in, if any
+ TextBuffer *drag_buffer;
// while a menu or something is open, there is no active buffer. when the menu is closed,
// the old active buffer needs to be restored. that's what this stores.
TextBuffer *prev_active_buffer;
@@ -104,5 +109,6 @@ typedef struct Ted {
TextBuffer line_buffer; // general-purpose line buffer for inputs -- used for menus
TextBuffer main_buffer;
KeyAction key_actions[KEY_COMBO_COUNT];
+ char cwd[TED_PATH_MAX]; // current working directory
char error[256];
} Ted;
diff --git a/ui.c b/ui.c
new file mode 100644
index 0000000..58f76d9
--- /dev/null
+++ b/ui.c
@@ -0,0 +1,234 @@
+// 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 // make room for cwd
+ + char_height * 1.5f // make room for line buffer
+ + 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[i].path);
+ }
+ free(fs->entries);
+ fs->entries = NULL;
+ fs->n_entries = 0;
+}
+
+static void file_selector_free(FileSelector *fs) {
+ file_selector_clear_entries(fs);
+ arr_clear(fs->cwd);
+}
+
+static int qsort_file_entry_cmp(void const *av, void const *bv) {
+ FileEntry const *a = av, *b = bv;
+ // put directories first
+ if (a->type > b->type) {
+ return -1;
+ }
+ if (a->type < b->type) {
+ return +1;
+ }
+ return strcmp_case_insensitive(a->name, b->name);
+}
+
+// change directory of file selector.
+void file_selector_cd(FileSelector *fs, char const *path) {
+ // @TODO: handle .. properly
+ if (path[0] == PATH_SEPARATOR
+#if _WIN32
+ // check for, e.g. C:\ at start of path
+ || (strlen(path) >= 3 && path[1] == ':' && path[2] == '\\')
+#endif
+ ) {
+ // this is an absolute path. discard our previous cwd.
+ arr_clear(fs->cwd);
+ }
+ if (strlen(fs->cwd) > 0 && fs->cwd[strlen(fs->cwd) - 1] != PATH_SEPARATOR) {
+ // add path separator to end if not already there
+ arr_append_str(fs->cwd, PATH_SEPARATOR_STR);
+ }
+ arr_append_str(fs->cwd, path);
+}
+
+// returns the name of the selected file, or NULL
+// if none was selected. the returned pointer should be freed.
+static char *file_selector_update(Ted *ted, FileSelector *fs) {
+ String32 search_term32 = buffer_get_line(&ted->line_buffer, 0);
+ if (!fs->cwd) {
+ // set the file selector's directory to our current directory.
+ arr_append_str(fs->cwd, ted->cwd);
+ }
+ char *search_term = search_term32.len ? str32_to_utf8_cstr(search_term32) : NULL;
+
+ bool submitted = fs->submitted;
+ fs->submitted = false;
+
+ bool on_screen = true;
+ for (u32 i = 0; i < fs->n_entries; ++i) {
+ Rect r = {0};
+ FileEntry *entry = &fs->entries[i];
+ char *name = entry->name, *path = entry->path;
+ FsType type = entry->type;
+
+ // check if this entry was clicked on
+ if (on_screen && 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
+ switch (type) {
+ case FS_FILE:
+ free(search_term);
+ if (path) return str_dup(path);
+ break;
+ case FS_DIRECTORY:
+ file_selector_cd(fs, name);
+ break;
+ default: break;
+ }
+ }
+ }
+ } else on_screen = false;
+
+ // check if we submitted this entry
+ if (submitted && streq(search_term, name)) {
+ switch (type) {
+ case FS_FILE:
+ free(search_term);
+ if (path) return str_dup(path);
+ break;
+ case FS_DIRECTORY:
+ file_selector_cd(fs, name);
+ break;
+ default: break;
+ }
+ }
+ }
+
+ // user pressed enter after typing a non-existent file into the search bar
+ if (submitted) {
+ // don't do anything for now
+ }
+
+ // free previous entries
+ file_selector_clear_entries(fs);
+ // get new entries
+ char **files = fs_list_directory(fs->cwd);
+ if (files) {
+ char const *cwd = fs->cwd;
+ bool cwd_has_path_sep = cwd[strlen(cwd) - 1] == PATH_SEPARATOR;
+ u32 nfiles;
+ for (nfiles = 0; files[nfiles]; ++nfiles);
+
+ // filter entries
+ bool increment = true;
+ for (u32 i = 0; i < nfiles; i += increment, increment = true) {
+ // remove if the file name does not contain the search term,
+ bool remove = search_term && *search_term && !stristr(files[i], search_term);
+ // or if this is just the current directory
+ remove |= streq(files[i], ".");
+ if (remove) {
+ // remove this one
+ free(files[i]);
+ --nfiles;
+ if (nfiles) {
+ files[i] = files[nfiles];
+ }
+ increment = false;
+ }
+ }
+
+ 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) {
+ char *name = files[i];
+ entries[i].name = name;
+ // add cwd to start of file name
+ size_t path_size = strlen(name) + strlen(cwd) + 3;
+ char *path = ted_calloc(ted, 1, path_size);
+ if (path) {
+ snprintf(path, path_size - 1, "%s%s%s", cwd, cwd_has_path_sep ? PATH_SEPARATOR_STR : "", name);
+ entries[i].path = path;
+ entries[i].type = fs_path_type(path);
+ } else {
+ entries[i].path = NULL; // what can we do?
+ entries[i].type = FS_NON_EXISTENT;
+ }
+ }
+ }
+ qsort(entries, nfiles, sizeof *entries, qsort_file_entry_cmp);
+ }
+
+ free(files);
+ } else {
+ ted_seterr(ted, "Couldn't list directory '%s'.", fs->cwd);
+ }
+
+ free(search_term);
+ return NULL;
+}
+
+static void file_selector_render(Ted *ted, FileSelector *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);
+
+ // current working directory @TODO
+
+
+ // search buffer
+ float line_buffer_height = char_height * 1.5f;
+ buffer_render(&ted->line_buffer, x1, y1, x2, y1 + line_buffer_height);
+ y1 += line_buffer_height;
+
+
+ for (u32 i = 0; i < n_entries; ++i) {
+ // highlight entry user is mousing over
+ Rect r;
+ if (!file_selector_entry_pos(ted, fs, i, &r)) break;
+ if (rect_contains_point(r, ted->mouse_pos)) {
+ glBegin(GL_QUADS);
+ gl_color_rgba(colors[COLOR_MENU_HL]);
+ rect_render(r);
+ glEnd();
+ }
+ }
+
+ 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) {
+ Rect r;
+ if (!file_selector_entry_pos(ted, fs, i, &r)) break;
+ float x = r.pos.x, y = r.pos.y;
+ switch (entries[i].type) {
+ case FS_FILE:
+ gl_color_rgba(colors[COLOR_TEXT]);
+ break;
+ case FS_DIRECTORY:
+ gl_color_rgba(colors[COLOR_TEXT_FOLDER]);
+ break;
+ default:
+ gl_color_rgba(colors[COLOR_TEXT_OTHER]);
+ break;
+ }
+ text_render_with_state(font, &text_render_state, entries[i].name, x, y);
+ }
+}
+