From 16ab3629d435d52539b6345d79433e27c367ce83 Mon Sep 17 00:00:00 2001 From: Leo Tenenbaum Date: Sun, 3 Jan 2021 20:55:42 -0500 Subject: make install, command line arguments --- Makefile | 17 ++++++- Untitled | 4 ++ buffer.c | 25 ++++++++-- config.c | 17 +++---- filesystem.c | 19 ++++++++ main.c | 156 +++++++++++++++++++++++++++++++++++++++++++++++++++-------- test.txt | 5 -- util.c | 105 +++++++++++++++++++++++++++++++++++++++- 8 files changed, 303 insertions(+), 45 deletions(-) create mode 100644 Untitled create mode 100644 filesystem.c delete mode 100644 test.txt diff --git a/Makefile b/Makefile index 0b720c0..6dc6b3e 100644 --- a/Makefile +++ b/Makefile @@ -2,9 +2,24 @@ ALL_CFLAGS=$(CFLAGS) -Wall -Wextra -Wshadow -Wconversion -Wpedantic -pedantic -s -Wno-unused-function -Wno-fixed-enum-extension -Wimplicit-fallthrough LIBS=-lSDL2 -lGL -ldl -lm DEBUG_CFLAGS=$(ALL_CFLAGS) -DDEBUG -O0 -g +RELEASE_CFLAGS=$(ALL_CFLAGS) -O3 +GLOBAL_DATA_DIR=/usr/share/ted +LOCAL_DATA_DIR=/home/`logname`/.local/share/ted +INSTALL_BIN_DIR=/usr/bin ted: *.[ch] text.o - $(CC) main.c text.o -o $@ $(DEBUG_CFLAGS) $(LIBS) + $(CC) main.c text.o -o ted $(DEBUG_CFLAGS) $(LIBS) +release: *.[ch] + $(CC) main.c text.o -o ted $(RELEASE_CFLAGS) $(LIBS) text.o: text.c text.h base.h lib/stb_truetype.h $(CC) text.c -c -o $@ $(DEBUG_CFLAGS) clean: rm -f ted *.o +install: release + @[ -w `dirname $(GLOBAL_DATA_DIR)` ] || { echo "You need permission to write to $(GLOBAL_DATA_DIR). Try running with sudo/as root." && exit 1; } + @[ -w `dirname $(INSTALL_BIN_DIR)` ] || { echo "You need permission to write to $(INSTALL_BIN_DIR). Try running with sudo/as root." && exit 1; } + + mkdir -p $(GLOBAL_DATA_DIR) $(LOCAL_DATA_DIR) + cp -r assets $(GLOBAL_DATA_DIR) + install -m 644 ted.cfg $(GLOBAL_DATA_DIR) + [ ! -e $(LOCAL_DATA_DIR)/ted.cfg ] && install -o `logname` -g `logname` -m 644 ted.cfg $(LOCAL_DATA_DIR) || : + install ted $(INSTALL_BIN_DIR) diff --git a/Untitled b/Untitled new file mode 100644 index 0000000..e1305f3 --- /dev/null +++ b/Untitled @@ -0,0 +1,4 @@ +1 2 3 4 +t e s t +he ll o!! !! + diff --git a/buffer.c b/buffer.c index 8ad3733..5fbcd2e 100644 --- a/buffer.c +++ b/buffer.c @@ -448,6 +448,7 @@ static void buffer_line_free(Line *line) { free(line->str); } +// Free a buffer. Once a buffer is freed, it can be used again for other purposes. // Does not free the pointer `buffer` (buffer might not have even been allocated with malloc) void buffer_free(TextBuffer *buffer) { Line *lines = buffer->lines; @@ -465,16 +466,23 @@ void buffer_free(TextBuffer *buffer) { arr_free(buffer->undo_history); arr_free(buffer->redo_history); + Settings *settings = buffer->settings; + Font *font = buffer->font; + // zero buffer, except for error char error[sizeof buffer->error]; memcpy(error, buffer->error, sizeof error); memset(buffer, 0, sizeof *buffer); memcpy(buffer->error, error, sizeof error); + + buffer_create(buffer, font, settings); } // filename must be around for at least as long as the buffer is. -Status buffer_load_file(TextBuffer *buffer, char const *filename) { +void buffer_load_file(TextBuffer *buffer, char const *filename) { + buffer_free(buffer); + buffer->filename = filename; FILE *fp = fopen(filename, "rb"); if (fp) { @@ -483,7 +491,7 @@ Status buffer_load_file(TextBuffer *buffer, char const *filename) { fseek(fp, 0, SEEK_SET); if (file_size > 10L<<20) { buffer_seterr(buffer, "File too big (size: %zu).", file_size); - return false; + return; } u8 *file_contents = buffer_calloc(buffer, 1, file_size); @@ -544,13 +552,20 @@ Status buffer_load_file(TextBuffer *buffer, char const *filename) { if (!success) { buffer_free(buffer); } - return success; } else { buffer_seterr(buffer, "File %s does not exist.", filename); - return false; } } +void buffer_new_file(TextBuffer *buffer, char const *filename) { + buffer_free(buffer); + + buffer->filename = filename; + buffer->lines_capacity = 4; + buffer->lines = buffer_calloc(buffer, buffer->lines_capacity, sizeof *buffer->lines); + buffer->nlines = 1; +} + bool buffer_save(TextBuffer *buffer) { FILE *out = fopen(buffer->filename, "wb"); if (out) { @@ -1743,7 +1758,7 @@ void buffer_render(TextBuffer *buffer, float x1, float y1, float x2, float y2) { char32_t c = *p; switch (c) { - case U'\n': assert(0); + case U'\n': assert(0); break; case U'\r': break; // for CRLF line endings case U'\t': { uint tab_width = buffer->settings->tab_width; diff --git a/config.c b/config.c index 8431da3..9cca619 100644 --- a/config.c +++ b/config.c @@ -31,21 +31,21 @@ static u32 config_parse_key_combo(ConfigReader *cfg, char const *str) { u32 modifier = 0; // read modifier while (true) { - if (util_is_prefix(str, "Ctrl+")) { + if (str_is_prefix(str, "Ctrl+")) { if (modifier & KEY_MODIFIER_CTRL) { config_err(cfg, "Ctrl+ written twice"); return 0; } modifier |= KEY_MODIFIER_CTRL; str += strlen("Ctrl+"); - } else if (util_is_prefix(str, "Shift+")) { + } else if (str_is_prefix(str, "Shift+")) { if (modifier & KEY_MODIFIER_SHIFT) { config_err(cfg, "Shift+ written twice"); return 0; } modifier |= KEY_MODIFIER_SHIFT; str += strlen("Shift+"); - } else if (util_is_prefix(str, "Alt+")) { + } else if (str_is_prefix(str, "Alt+")) { if (modifier & KEY_MODIFIER_ALT) { config_err(cfg, "Alt+ written twice"); return 0; @@ -239,14 +239,9 @@ void config_read(Ted *ted, char const *filename) { // read the command Command command = command_from_str(value + 1); if (command != CMD_UNKNOWN) { - if (action->command) { - config_err(cfg, "Key binding for %s set twice (first time was on line " U32_FMT ").", - key, action->line_number); - } else { - action->command = command; - action->argument = argument; - action->line_number = cfg->line_number; - } + action->command = command; + action->argument = argument; + action->line_number = cfg->line_number; } else { config_err(cfg, "Unrecognized command %s", value); } diff --git a/filesystem.c b/filesystem.c new file mode 100644 index 0000000..f68fe72 --- /dev/null +++ b/filesystem.c @@ -0,0 +1,19 @@ +#include +#include +#if __unix__ +#include +#endif + +static bool fs_file_exists(char const *path) { +#if _WIN32 + struct _stat statbuf = {0}; + if (_stat(path, &statbuf) != 0) + return false; + return statbuf.st_mode == _S_IFREG; +#else + struct stat statbuf = {0}; + if (stat(path, &statbuf) != 0) + return false; + return S_ISREG(statbuf.st_mode); +#endif +} diff --git a/main.c b/main.c index 05341d5..41a9a98 100644 --- a/main.c +++ b/main.c @@ -1,5 +1,6 @@ // @TODO: // - text size (text-size, :increase-text-size, :decrease-text-size) +// - put stuff in UserData on windows #include "base.h" no_warn_start #if _WIN32 @@ -11,9 +12,15 @@ no_warn_end #include #include #include +#if _WIN32 +#include +#endif + +#define TED_PATH_MAX 256 #include "command.h" #include "util.c" +#include "filesystem.c" #include "colors.c" typedef struct { float cursor_blink_time_on, cursor_blink_time_off; @@ -50,14 +57,98 @@ static void die(char const *fmt, ...) { exit(EXIT_FAILURE); } +// should the working directory be searched for files? set to true if the executable isn't "installed" +static bool ted_search_cwd = false; +#if _WIN32 +// @TODO +#else +static char const *const ted_global_data_dir = "/usr/share/ted"; +#endif + +// Check the various places a file could be, and return the full path. +static Status ted_get_file(char const *name, char *out, size_t outsz) { +#if _WIN32 + #error "@TODO(windows)" +#else + if (ted_search_cwd && fs_file_exists(name)) { + // check in current working directory + str_cpy(out, outsz, name); + return true; + } + + char *home = getenv("HOME"); + if (home) { + str_printf(out, outsz, "%s/.local/share/ted/%s", home, name); + if (!fs_file_exists(out)) { + str_printf(out, outsz, "%s/%s", ted_global_data_dir, name); + if (!fs_file_exists(out)) + return false; + } + } + return true; +#endif +} #if _WIN32 INT WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR lpCmdLine, INT nCmdShow) { + int argc = 0; + LPWSTR* wide_argv = CommandLineToArgvW(GetCommandLineW(), &argc); + char** argv = malloc(argc * sizeof *argv); + if (!argv) { + die("Out of memory."); + } + for (int i = 0; i < argc; i++) { + LPWSTR wide_arg = wide_args[i]; + int len = wcslen(wide_arg); + argv[i] = malloc(len + 1); + if (!argv[i]) die("Out of memory."); + for (int j = 0; j <= len; j++) + argv[i][j] = (char)wide_arg[j]; + } + LocalFree(wide_args); #else -int main(void) { +int main(int argc, char **argv) { #endif setlocale(LC_ALL, ""); // allow unicode + + { // 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}; + #if _WIN32 + // @TODO(windows): GetModuleFileNameW + #else + ssize_t len = readlink("/proc/self/exe", executable_path, sizeof executable_path - 1); + executable_path[len] = '\0'; + ted_search_cwd = !str_is_prefix(executable_path, "/usr"); + #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/ted.cfg", ted_global_data_dir); + if (fs_file_exists(global_config_filename)) + config_read(ted, global_config_filename); + } + { + // read local configuration file + char config_filename[TED_PATH_MAX]; + if (ted_get_file("ted.cfg", config_filename, sizeof config_filename)) + config_read(ted, config_filename); + else + ted_seterr(ted, "Couldn't find config file (ted.cfg), not even the backup one that should have come with ted."); + } + if (ted_haserr(ted)) { + die("Error reading config: %s", ted_geterr(ted)); + } + SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1"); // if this program is sent a SIGTERM/SIGINT, don't turn it into a quit event if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER) < 0) die("%s", SDL_GetError()); @@ -68,9 +159,12 @@ int main(void) { die("%s", SDL_GetError()); { // set icon - SDL_Surface *icon = SDL_LoadBMP("assets/icon.bmp"); - SDL_SetWindowIcon(window, icon); - SDL_FreeSurface(icon); + char icon_filename[TED_PATH_MAX]; + if (ted_get_file("assets/icon.bmp", icon_filename, sizeof icon_filename)) { + SDL_Surface *icon = SDL_LoadBMP(icon_filename); + SDL_SetWindowIcon(window, icon); + SDL_FreeSurface(icon); + } // if we can't find the icon file, it's no big deal } SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); @@ -82,30 +176,45 @@ int main(void) { SDL_GL_SetSwapInterval(1); // vsync - Font *font = text_font_load("assets/font.ttf", 16); - if (!font) { - die("Couldn't load font: %s", text_get_err()); + Font *font = NULL; + { + char font_filename[TED_PATH_MAX]; + if (ted_get_file("assets/font.ttf", font_filename, sizeof font_filename)) { + font = text_font_load(font_filename, 16); + if (!font) { + die("Couldn't load font: %s", text_get_err()); + } + } else { + die("Couldn't find font file. There is probably a problem with your ted installation."); + } } - Ted *ted = calloc(1, sizeof *ted); - if (!ted) { - die("Not enough memory available to run ted."); - } - - Settings *settings = &ted->settings; - - config_read(ted, "ted.cfg"); - if (ted_haserr(ted)) { - die("Error reading config: %s", ted_geterr(ted)); - } - TextBuffer text_buffer; TextBuffer *buffer = &text_buffer; buffer_create(buffer, font, settings); ted->active_buffer = buffer; - if (!buffer_load_file(buffer, "buffer.c")) - die("Error loading file: %s", buffer_geterr(buffer)); + char const *starting_filename = "Untitled"; + + switch (argc) { + case 0: case 1: break; + case 2: + starting_filename = argv[1]; + break; + default: + die("Usage: %s [filename]", argv[0]); + break; + } + + if (fs_file_exists(starting_filename)) { + buffer_load_file(buffer, starting_filename); + if (buffer_haserr(buffer)) + die("Error loading file: %s", buffer_geterr(buffer)); + } else { + buffer_new_file(buffer, starting_filename); + if (buffer_haserr(buffer)) + die("Error creating file: %s", buffer_geterr(buffer)); + } Uint32 time_at_last_frame = SDL_GetTicks(); @@ -262,6 +371,11 @@ int main(void) { buffer_free(buffer); text_font_free(font); free(ted); +#if _WIN32 + for (int i = 0; i < argc; ++i) + free(argv[i]); + free(argv); +#endif return 0; } diff --git a/test.txt b/test.txt deleted file mode 100644 index 306195f..0000000 --- a/test.txt +++ /dev/null @@ -1,5 +0,0 @@ -3 33 100 4 -7 1 2 88 -3 33 333 3333 -100 49 388 101 -N/A 1 N/A N/A \ No newline at end of file diff --git a/util.c b/util.c index 06606e3..4041511 100644 --- a/util.c +++ b/util.c @@ -39,10 +39,111 @@ static char32_t const *util_mem32chr_const(char32_t const *s, char32_t c, size_t return NULL; } +static bool str_is_prefix(char const *str, char const *prefix) { + return strncmp(str, prefix, strlen(prefix)) == 0; +} + static bool streq(char const *a, char const *b) { return strcmp(a, b) == 0; } -static bool util_is_prefix(char const *str, char const *prefix) { - return strncmp(str, prefix, strlen(prefix)) == 0; +// like snprintf, but not screwed up on windows +#define str_printf(str, size, ...) (str)[(size) - 1] = '\0', snprintf((str), (size) - 1, __VA_ARGS__) +// like snprintf, but the size is taken to be the length of the array str. +// first, check that str is actually an array +#define strbuf_printf(str, ...) assert(sizeof str != 4 && sizeof str != 8), \ + str_printf(str, sizeof str, __VA_ARGS__) + +// on 16-bit systems, this is 16383. on 32/64-bit systems, this is 1073741823 +// it is unusual to have a string that long. +#define STRLEN_SAFE_MAX (UINT_MAX >> 2) + +// safer version of strcat. dst_sz includes a null terminator. +static void str_cat(char *dst, size_t dst_sz, char const *src) { + size_t dst_len = strlen(dst), src_len = strlen(src); + + // make sure dst_len + src_len + 1 doesn't overflow + if (dst_len > STRLEN_SAFE_MAX || src_len > STRLEN_SAFE_MAX) { + assert(0); + return; + } + + if (dst_len >= dst_sz) { + // dst doesn't actually contain a null-terminated string! + assert(0); + return; + } + + if (dst_len + src_len + 1 > dst_sz) { + // number of bytes left in dst, not including null terminator + size_t n = dst_sz - dst_len - 1; + memcpy(dst + dst_len, src, n); + dst[dst_sz - 1] = 0; // dst_len + n == dst_sz - 1 + } else { + memcpy(dst + dst_len, src, src_len); + dst[dst_len + src_len] = 0; + } } + +// safer version of strncpy. dst_sz includes a null terminator. +static void str_cpy(char *dst, size_t dst_sz, char const *src) { + size_t srclen = strlen(src); + size_t n = srclen; // number of bytes to copy + + if (dst_sz == 0) { + assert(0); + return; + } + + if (dst_sz-1 < n) + n = dst_sz-1; + memcpy(dst, src, n); + dst[n] = 0; +} + +/* +returns the first instance of needle in haystack, 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; + + if (needle_len > haystack_len) return NULL; // a larger string can't fit in a smaller string + + for (i = 0; i <= haystack_len - needle_len; ++i) { + char const *p = haystack + i, *q = needle; + bool match = true; + for (j = 0; j < needle_len; ++j) { + if (tolower(*p) != tolower(*q)) { + match = false; + break; + } + ++p; + ++q; + } + if (match) + return (char *)haystack + i; + } + return NULL; +} + +static void print_bytes(u8 *bytes, size_t n) { + u8 *b, *end; + for (b = bytes, end = bytes + n; b != end; ++b) + printf("%x ", *b); + printf("\n"); +} + +/* +does this predicate hold for all the characters of s. predicate is int (*)(int) instead +of bool (*)(char) so that you can pass isprint, etc. to it. +*/ +static bool str_satisfies(char const *s, int (*predicate)(int)) { + char const *p; + for (p = s; *p; ++p) + if (!predicate(*p)) + return false; + return true; +} + -- cgit v1.2.3