From 8fca7beaf35cfc438d5d29f352f80dd18efe7d2e Mon Sep 17 00:00:00 2001 From: Leo Tenenbaum Date: Thu, 21 Jan 2021 13:35:18 -0500 Subject: file selector now actually working also made stristr work with UTF-8 --- buffer.c | 8 ++++++-- filesystem.h | 3 ++- main.c | 2 ++ menu.c | 37 ++++++++++++++++++++++++++++-------- ted.cfg | 2 +- util.c | 62 ++++++++++++++++++++++++++++++++++++++++++------------------ 6 files changed, 84 insertions(+), 30 deletions(-) diff --git a/buffer.c b/buffer.c index 162547a..520d7d2 100644 --- a/buffer.c +++ b/buffer.c @@ -483,9 +483,13 @@ Status buffer_load_file(TextBuffer *buffer, char const *filename) { bool success = true; if (fp) { fseek(fp, 0, SEEK_END); - size_t file_size = (size_t)ftell(fp); + long file_pos = ftell(fp); + size_t file_size = (size_t)file_pos; fseek(fp, 0, SEEK_SET); - if (file_size > 10L<<20) { + if (file_pos == -1 || file_pos == LONG_MAX) { + buffer_seterr(buffer, "Couldn't get file position. There is something wrong with the file '%s'.", filename); + success = false; + } else if (file_size > 10L<<20) { buffer_seterr(buffer, "File too big (size: %zu).", file_size); success = false; } else { diff --git a/filesystem.h b/filesystem.h index 424b824..238ebb1 100644 --- a/filesystem.h +++ b/filesystem.h @@ -23,4 +23,5 @@ char **fs_list_directory(char const *dirname); // -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); -#endif // FILESYSTEM_H_ \ No newline at end of file +#endif // FILESYSTEM_H_ + diff --git a/main.c b/main.c index f014d89..39e582b 100644 --- a/main.c +++ b/main.c @@ -81,6 +81,8 @@ int main(int argc, char **argv) { #endif setlocale(LC_ALL, ""); // allow unicode + printf("%d\n", !!stristr("foo", "x")); + { // get local data directory #if _WIN32 wchar_t *appdata = NULL; diff --git a/menu.c b/menu.c index 9c194c1..4d7e965 100644 --- a/menu.c +++ b/menu.c @@ -57,6 +57,18 @@ 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) { @@ -83,16 +95,23 @@ static FileEntry file_selector_update(Ted *ted, FileSelector *fs, String32 const 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]; + + // 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; } - nfiles = out; } if (nfiles) { @@ -105,6 +124,7 @@ static FileEntry file_selector_update(Ted *ted, FileSelector *fs, String32 const entries[i].type = fs_path_type(files[i]); } } + qsort(entries, nfiles, sizeof *entries, qsort_file_entry_cmp); } } else { #if DEBUG @@ -156,6 +176,7 @@ static void file_selector_render(Ted const *ted, FileSelector const *fs) { break; case FS_DIRECTORY: gl_color_rgba(colors[COLOR_TEXT_FOLDER]); + break; default: gl_color_rgba(colors[COLOR_TEXT_OTHER]); break; diff --git a/ted.cfg b/ted.cfg index c4cf97c..05ea53f 100644 --- a/ted.cfg +++ b/ted.cfg @@ -80,4 +80,4 @@ bg = #001 # By making it transparent, we can dim everything else while the menu is open. menu-backdrop = #0004 menu-bg = #222 -menu-hl = #888 \ No newline at end of file +menu-hl = #666 diff --git a/util.c b/util.c index 8c1239c..9dcb5ea 100644 --- a/util.c +++ b/util.c @@ -105,29 +105,50 @@ static void str_cpy(char *dst, size_t dst_sz, char const *src) { dst[n] = 0; } +// advances str to the start of the next UTF8 character +static void utf8_next_char_const(char const **str) { + if (**str) { + do { + ++*str; + } while (((u8)(**str) & 0xC0) == 0x80); // while we are on a continuation byte + } +} + /* -returns the first instance of needle in haystack, ignoring the case of the characters, +returns the first instance of needle in haystack, where both are UTF-8 strings, ignoring the case of the characters, or NULL if the haystack does not contain needle WARNING: O(strlen(haystack) * strlen(needle)) */ static char *stristr(char const *haystack, char const *needle) { - size_t needle_len = strlen(needle), haystack_len = strlen(haystack), i, j; + size_t needle_bytes = strlen(needle), haystack_bytes = strlen(haystack); + + if (needle_bytes > haystack_bytes) return NULL; - if (needle_len > haystack_len) return NULL; // a larger string can't fit in a smaller string + char const *haystack_end = haystack + haystack_bytes; + char const *needle_end = needle + needle_bytes; - for (i = 0; i <= haystack_len - needle_len; ++i) { - char const *p = haystack + i, *q = needle; + for (char const *haystack_start = haystack; haystack_start + needle_bytes <= haystack_end; utf8_next_char_const(&haystack_start)) { + char const *p = haystack_start, *q = needle; + mbstate_t pstate = {0}, qstate = {0}; bool match = true; - for (j = 0; j < needle_len; ++j) { - if (tolower(*p) != tolower(*q)) { - match = false; - break; - } - ++p; - ++q; + + // check if p matches q + while (q < needle_end) { + char32_t pchar = 0, qchar = 0; + size_t bytes_p = mbrtoc32(&pchar, p, (size_t)(haystack_end - p), &pstate); + size_t bytes_q = mbrtoc32(&qchar, q, (size_t)(needle_end - q), &qstate); + if (bytes_p == (size_t)-3) bytes_p = 0; + if (bytes_q == (size_t)-3) bytes_q = 0; + if (bytes_p > (size_t)-3 || bytes_q > (size_t)-3) return NULL; // invalid UTF-8 + bool same = pchar == qchar; + if (pchar < WINT_MAX && qchar < WINT_MAX) // on Windows, there is no way of finding the lower-case version of a codepoint outside the BMP. ): + same = towlower((wint_t)pchar) == towlower((wint_t)qchar); + if (!same) match = false; + p += bytes_p; + q += bytes_q; } if (match) - return (char *)haystack + i; + return (char *)haystack_start; } return NULL; } @@ -151,12 +172,17 @@ static bool str_satisfies(char const *s, int (*predicate)(int)) { return true; } -// function to be passed into qsort for case insensitive sorting -static int str_qsort_case_insensitive_cmp(const void *av, const void *bv) { - char const *const *a = av, *const *b = bv; + +static int strcmp_case_insensitive(char const *a, char const *b) { #if _WIN32 - return _stricmp(*a, *b); + return _stricmp(a, b); #else - return strcasecmp(*a, *b); + return strcasecmp(a, b); #endif } + +// function to be passed into qsort for case insensitive sorting +static int str_qsort_case_insensitive_cmp(const void *av, const void *bv) { + char const *const *a = av, *const *b = bv; + return strcmp_case_insensitive(*a, *b); +} -- cgit v1.2.3