diff options
author | Leo Tenenbaum <pommicket@gmail.com> | 2021-01-18 16:37:51 -0500 |
---|---|---|
committer | Leo Tenenbaum <pommicket@gmail.com> | 2021-01-18 16:37:51 -0500 |
commit | a3adbe5ea6015a76a3df84ee5942b89fbb762947 (patch) | |
tree | 712d0fdfaf23818ebf2b361306dbb9d4452e6540 | |
parent | 37102a766e1913cd0548a981e5c601852ae47963 (diff) |
opening files kinda working
-rw-r--r-- | buffer.c | 149 | ||||
-rw-r--r-- | command.c | 28 | ||||
-rw-r--r-- | command.h | 5 | ||||
-rw-r--r-- | main.c | 75 | ||||
-rw-r--r-- | menu.c | 34 | ||||
-rw-r--r-- | string32.c | 41 | ||||
-rw-r--r-- | ted-base.c | 13 | ||||
-rw-r--r-- | ted.cfg | 1 | ||||
-rw-r--r-- | ted.h | 9 | ||||
-rw-r--r-- | util.c | 9 |
10 files changed, 247 insertions, 117 deletions
@@ -1,11 +1,5 @@ // Text buffers - These store the contents of a file. -void buffer_create(TextBuffer *buffer, Ted *ted) { - util_zero_memory(buffer, sizeof *buffer); - buffer->store_undo_events = true; - buffer->ted = ted; -} - // this is a macro so we get -Wformat warnings #define buffer_seterr(buffer, ...) \ snprintf(buffer->error, sizeof buffer->error - 1, __VA_ARGS__) @@ -73,6 +67,27 @@ static void *buffer_realloc(TextBuffer *buffer, void *p, size_t new_size) { return ret; } +static char *buffer_strdup(TextBuffer *buffer, char const *src) { + char *dup = str_dup(src); + if (!dup) buffer_out_of_mem(buffer); + return dup; +} + +void buffer_create(TextBuffer *buffer, Ted *ted) { + util_zero_memory(buffer, sizeof *buffer); + buffer->store_undo_events = true; + buffer->ted = ted; +} + +void line_buffer_create(TextBuffer *buffer, Ted *ted) { + buffer_create(buffer, ted); + buffer->is_line_buffer = true; + if ((buffer->lines = buffer_calloc(buffer, 1, sizeof *buffer->lines))) { + buffer->nlines = 1; + buffer->lines_capacity = 1; + } +} + // ensures that `p` refers to a valid position. static void buffer_pos_validate(TextBuffer *buffer, BufferPos *p) { if (p->line >= buffer->nlines) @@ -417,7 +432,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. +// Free a buffer. Once a buffer is freed, you can call buffer_create on it again. // Does not free the pointer `buffer` (buffer might not have even been allocated with malloc) void buffer_free(TextBuffer *buffer) { Line *lines = buffer->lines; @@ -426,6 +441,7 @@ void buffer_free(TextBuffer *buffer) { buffer_line_free(&lines[i]); } free(lines); + free(buffer->filename); arr_foreach_ptr(buffer->undo_history, BufferEdit, edit) buffer_edit_free(edit); @@ -434,24 +450,27 @@ void buffer_free(TextBuffer *buffer) { arr_free(buffer->undo_history); arr_free(buffer->redo_history); + util_zero_memory(buffer, sizeof *buffer); +} +// clear contents, undo history, etc. of a buffer +void buffer_clear(TextBuffer *buffer) { + bool is_line_buffer = buffer->is_line_buffer; Ted *ted = buffer->ted; - - // 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, ted); + buffer_free(buffer); + if (is_line_buffer) { + line_buffer_create(buffer, ted); + } else { + buffer_create(buffer, ted); + } } - -// filename must be around for at least as long as the buffer is. void buffer_load_file(TextBuffer *buffer, char const *filename) { - buffer_free(buffer); + buffer_clear(buffer); - buffer->filename = filename; + buffer->filename = buffer_strdup(buffer, filename); FILE *fp = fopen(filename, "rb"); if (fp) { fseek(fp, 0, SEEK_END); @@ -518,7 +537,7 @@ void buffer_load_file(TextBuffer *buffer, char const *filename) { success = false; } if (!success) { - buffer_free(buffer); + buffer_clear(buffer); } } else { buffer_seterr(buffer, "File %s does not exist.", filename); @@ -526,35 +545,50 @@ void buffer_load_file(TextBuffer *buffer, char const *filename) { } void buffer_new_file(TextBuffer *buffer, char const *filename) { - buffer_free(buffer); + buffer_clear(buffer); - buffer->filename = filename; + buffer->filename = buffer_strdup(buffer, 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) { - bool success = true; - for (Line *line = buffer->lines, *end = line + buffer->nlines; line != end; ++line) { - mbstate_t state = {0}; - for (char32_t *p = line->str, *p_end = p + line->len; p != p_end; ++p) { - char utf8[MB_LEN_MAX] = {0}; - size_t bytes = c32rtomb(utf8, *p, &state); - fwrite(utf8, 1, bytes, out); - } + if (buffer->filename) { + FILE *out = fopen(buffer->filename, "wb"); + if (out) { + bool success = true; + for (Line *line = buffer->lines, *end = line + buffer->nlines; line != end; ++line) { + mbstate_t state = {0}; + for (char32_t *p = line->str, *p_end = p + line->len; p != p_end; ++p) { + char utf8[MB_LEN_MAX] = {0}; + size_t bytes = c32rtomb(utf8, *p, &state); + fwrite(utf8, 1, bytes, out); + } - if (line != end-1) { - putc('\n', out); + if (line != end-1) { + putc('\n', out); + } } + if (ferror(out)) success = false; + if (fclose(out) != 0) success = false; + return success; + } else { + buffer_seterr(buffer, "Couldn't create file %s.", buffer->filename); + return false; } - if (ferror(out)) success = false; - if (fclose(out) != 0) success = false; - return success; } else { - buffer_seterr(buffer, "Couldn't create file %s.", buffer->filename); + // user tried to save line buffer. whatever + return true; + } +} + +// save, but with a different file name +bool buffer_save_as(TextBuffer *buffer, char const *new_filename) { + free(buffer->filename); + if ((buffer->filename = buffer_strdup(buffer, new_filename))) { + return buffer_save(buffer); + } else { return false; } } @@ -1073,7 +1107,9 @@ void buffer_cursor_move_to_end_of_file(TextBuffer *buffer) { } // insert `number` empty lines starting at index `where`. -static void buffer_insert_lines(TextBuffer *buffer, u32 where, u32 number) { +static Status buffer_insert_lines(TextBuffer *buffer, u32 where, u32 number) { + assert(!buffer->is_line_buffer); + u32 old_nlines = buffer->nlines; u32 new_nlines = old_nlines + number; if (buffer_lines_set_min_capacity(buffer, new_nlines)) { @@ -1085,7 +1121,9 @@ static void buffer_insert_lines(TextBuffer *buffer, u32 where, u32 number) { // zero new lines util_zero_memory(buffer->lines + where, number * sizeof *buffer->lines); buffer->nlines = new_nlines; + return true; } + return false; } // inserts the given text, returning the position of the end of the text @@ -1096,6 +1134,16 @@ BufferPos buffer_insert_text_at_pos(TextBuffer *buffer, BufferPos pos, String32 return ret; } + if (buffer->is_line_buffer) { + // remove all the newlines from str. + str32_remove_all_instances_of_char(&str, U'\n'); + } + + if (str.len == 0) { + // no text to insert + return pos; + } + if (buffer->store_undo_events) { BufferEdit *last_edit = arr_lastp(buffer->undo_history); i64 where_in_last_edit = last_edit ? buffer_pos_diff(buffer, last_edit->pos, pos) : -1; @@ -1121,16 +1169,17 @@ BufferPos buffer_insert_text_at_pos(TextBuffer *buffer, BufferPos pos, String32 // so we need to go through them one by one u32 n_added_lines = (u32)str32_count_char(str, U'\n'); if (n_added_lines) { - buffer_insert_lines(buffer, line_idx + 1, n_added_lines); - line = &buffer->lines[line_idx]; // fix pointer - // move any text past the cursor on this line to the last added line. - Line *last_line = &buffer->lines[line_idx + n_added_lines]; - u32 chars_moved = line->len - index; - if (chars_moved) { - if (buffer_line_set_min_capacity(buffer, last_line, chars_moved)) { - memcpy(last_line->str, line->str + index, chars_moved * sizeof(char32_t)); - line->len -= chars_moved; - last_line->len += chars_moved; + if (buffer_insert_lines(buffer, line_idx + 1, n_added_lines)) { + line = &buffer->lines[line_idx]; // fix pointer + // move any text past the cursor on this line to the last added line. + Line *last_line = &buffer->lines[line_idx + n_added_lines]; + u32 chars_moved = line->len - index; + if (chars_moved) { + if (buffer_line_set_min_capacity(buffer, last_line, chars_moved)) { + memcpy(last_line->str, line->str + index, chars_moved * sizeof(char32_t)); + line->len -= chars_moved; + last_line->len += chars_moved; + } } } } @@ -1635,10 +1684,11 @@ void buffer_render(TextBuffer *buffer, float x1, float y1, float x2, float y2) { }; - { // header + if (!buffer->is_line_buffer) { // header glColor3f(1,1,1); float x = x1, y = y1; - text_render_with_state(font, &text_state, buffer->filename, x, y); + if (buffer->filename) + text_render_with_state(font, &text_state, buffer->filename, x, y); #if DEBUG // show checksum char checksum[32] = {0}; @@ -1799,4 +1849,3 @@ void buffer_render(TextBuffer *buffer, float x1, float y1, float x2, float y2) { } } } - @@ -115,7 +115,7 @@ void command_execute(Ted *ted, Command c, i64 argument) { break; case CMD_OPEN: - ted_menu_open(ted, MENU_OPEN); + menu_open(ted, MENU_OPEN); break; case CMD_SAVE: if (buffer) buffer_save(buffer); @@ -144,11 +144,35 @@ void command_execute(Ted *ted, Command c, i64 argument) { case CMD_ESCAPE: if (ted->menu) { - ted_menu_close(ted, true); + menu_close(ted, true); } else if (buffer) { buffer_disable_selection(buffer); } break; + case CMD_SUBMIT_LINE_BUFFER: + if (buffer->is_line_buffer) { + switch (ted->menu) { + case MENU_NONE: + assert(0); + break; + case MENU_OPEN: { + TextBuffer *open_to = &ted->main_buffer; + String32 filename32 = {.str = buffer->lines[0].str, .len = buffer->lines[0].len}; + + char *filename_cstr = str32_to_utf8_cstr(filename32); + if (filename_cstr) { + buffer_load_file(open_to, filename_cstr); + buffer = open_to; + if (buffer_haserr(open_to)) { + // @TODO: something better + } + free(filename_cstr); + menu_close(ted, true); + } + } break; + } + } + break; } if (buffer && buffer_haserr(buffer)) { @@ -44,6 +44,8 @@ ENUM_U16 { CMD_ESCAPE, // by default this is the escape key. closes menus, etc. + CMD_SUBMIT_LINE_BUFFER, // submit "line buffer" value -- the line buffer is where you type file names when opening files, etc. + CMD_COUNT } ENUM_U16_END(Command); @@ -87,6 +89,7 @@ static CommandName const command_names[CMD_COUNT] = { {"redo", CMD_REDO}, {"increase-text-size", CMD_TEXT_SIZE_INCREASE}, {"decrease-text-size", CMD_TEXT_SIZE_DECREASE}, - {"escape", CMD_ESCAPE} + {"escape", CMD_ESCAPE}, + {"submit-line-buffer", CMD_SUBMIT_LINE_BUFFER} }; @@ -15,7 +15,6 @@ no_warn_end #include <shellapi.h> #endif -#define TED_PATH_MAX 256 #include "text.h" #include "util.c" @@ -178,12 +177,17 @@ int main(int argc, char **argv) { ted_load_font(ted); if (ted_haserr(ted)) - die("Error loadng font: %s", ted_geterr(ted)); + die("Error loading font: %s", ted_geterr(ted)); + { + TextBuffer *lbuffer = &ted->line_buffer; + line_buffer_create(lbuffer, ted); + if (buffer_haserr(lbuffer)) + die("Error creating line buffer: %s", buffer_geterr(lbuffer)); + } - TextBuffer text_buffer; { - TextBuffer *buffer = &text_buffer; + TextBuffer *buffer = &ted->main_buffer; buffer_create(buffer, ted); ted->active_buffer = buffer; @@ -216,15 +220,6 @@ int main(int argc, char **argv) { Uint32 time_at_last_frame = SDL_GetTicks(); bool quit = false; - bool ctrl_down = false; - bool shift_down = false; - bool alt_down = false; - { - char appdata[MAX_PATH] = {0}; - if (SHGetSpecialFolderPathA(NULL, appdata, CSIDL_LOCAL_APPDATA, false)) { - debug_println("%s", appdata); - } - } while (!quit) { #if DEBUG //printf("\033[H\033[2J"); @@ -241,12 +236,15 @@ int main(int argc, char **argv) { SDL_Event event; Uint8 const *keyboard_state = SDL_GetKeyboardState(NULL); + 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]; + while (SDL_PollEvent(&event)) { TextBuffer *buffer = ted->active_buffer; u32 key_modifier = (u32)ctrl_down << KEY_MODIFIER_CTRL_BIT | (u32)shift_down << KEY_MODIFIER_SHIFT_BIT | (u32)alt_down << KEY_MODIFIER_ALT_BIT; - // @TODO: make a function to handle text buffer events switch (event.type) { case SDL_QUIT: quit = true; @@ -295,18 +293,6 @@ int main(int argc, char **argv) { break; case SDL_KEYDOWN: { SDL_Scancode scancode = event.key.keysym.scancode; - switch (scancode) { - case SDL_SCANCODE_LCTRL: - case SDL_SCANCODE_RCTRL: - ctrl_down = true; break; - case SDL_SCANCODE_LSHIFT: - case SDL_SCANCODE_RSHIFT: - shift_down = true; break; - case SDL_SCANCODE_LALT: - case SDL_SCANCODE_RALT: - alt_down = true; break; - default: break; - } SDL_Keymod modifier = event.key.keysym.mod; u32 key_combo = (u32)scancode << 3 | (u32)((modifier & (KMOD_LCTRL|KMOD_RCTRL)) != 0) << KEY_MODIFIER_CTRL_BIT | @@ -314,33 +300,24 @@ int main(int argc, char **argv) { (u32)((modifier & (KMOD_LALT|KMOD_RALT)) != 0) << KEY_MODIFIER_ALT_BIT; if (key_combo < KEY_COMBO_COUNT) { KeyAction *action = &ted->key_actions[key_combo]; + bool was_in_line_buffer = buffer && buffer->is_line_buffer; if (action->command) { command_execute(ted, action->command, action->argument); - } else if (buffer) switch (event.key.keysym.sym) { - case SDLK_RETURN: - buffer_insert_char_at_cursor(buffer, U'\n'); + } + + if (buffer) { + switch (key_combo) { + case SDL_SCANCODE_RETURN << 3: + if (!was_in_line_buffer) // make sure return to submit line buffer doesn't get added to newly-active buffer + buffer_insert_char_at_cursor(buffer, U'\n'); break; - case SDLK_TAB: + case SDL_SCANCODE_TAB << 3: buffer_insert_char_at_cursor(buffer, U'\t'); break; + } } } } break; - case SDL_KEYUP: { - SDL_Scancode scancode = event.key.keysym.scancode; - switch (scancode) { - case SDL_SCANCODE_LCTRL: - case SDL_SCANCODE_RCTRL: - ctrl_down = false; break; - case SDL_SCANCODE_LSHIFT: - case SDL_SCANCODE_RSHIFT: - shift_down = false; break; - case SDL_SCANCODE_LALT: - case SDL_SCANCODE_RALT: - alt_down = false; break; - default: break; - } - } break; case SDL_TEXTINPUT: { char *text = event.text.text; if (buffer @@ -400,7 +377,7 @@ int main(int argc, char **argv) { { float x1 = 50, y1 = 50, x2 = window_width-50, y2 = window_height-50; - buffer_render(&text_buffer, x1, y1, x2, y2); + buffer_render(&ted->main_buffer, x1, y1, x2, y2); if (text_has_err()) { die("Text error: %s\n", text_get_err()); break; @@ -413,7 +390,8 @@ int main(int argc, char **argv) { } #if DEBUG - buffer_check_valid(&text_buffer); + buffer_check_valid(&ted->main_buffer); + buffer_check_valid(&ted->line_buffer); #endif SDL_GL_SwapWindow(window); @@ -422,7 +400,8 @@ int main(int argc, char **argv) { SDL_GL_DeleteContext(glctx); SDL_DestroyWindow(window); SDL_Quit(); - buffer_free(&text_buffer); + buffer_free(&ted->main_buffer); + buffer_free(&ted->line_buffer); text_font_free(ted->font); free(ted); #if _WIN32 @@ -1,3 +1,23 @@ +static void menu_open(Ted *ted, Menu menu) { + ted->menu = menu; + ted->prev_active_buffer = ted->active_buffer; + ted->active_buffer = NULL; + + switch (menu) { + case MENU_NONE: assert(0); break; + case MENU_OPEN: + ted->active_buffer = &ted->line_buffer; + break; + } +} + +static void menu_close(Ted *ted, bool restore_prev_active_buffer) { + ted->menu = MENU_NONE; + if (restore_prev_active_buffer) ted->active_buffer = ted->prev_active_buffer; + ted->prev_active_buffer = NULL; + + buffer_clear(&ted->line_buffer); +} static void menu_render(Ted *ted, Menu menu) { Settings *settings = &ted->settings; @@ -22,6 +42,7 @@ static void menu_render(Ted *ted, Menu menu) { float menu_y1 = padding; float menu_y2 = window_height - padding; Rect menu_rect = rect4(menu_x1, menu_y1, menu_x2, menu_y2); + float inner_padding = 10; // menu rectangle & border glBegin(GL_QUADS); @@ -30,6 +51,14 @@ static void menu_render(Ted *ted, Menu menu) { gl_color_rgba(colors[COLOR_BORDER]); rect_render_border(menu_rect, settings->border_thickness); glEnd(); + + float line_buffer_height = char_height * 1.5f; + float line_buffer_x1 = menu_x1 + inner_padding, + line_buffer_y1 = menu_y1 + inner_padding, + line_buffer_x2 = menu_x2 - inner_padding, + 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); char **files = fs_list_directory(directory); if (files) { @@ -38,8 +67,8 @@ static void menu_render(Ted *ted, Menu menu) { qsort(files, nfiles, sizeof *files, str_qsort_case_insensitive_cmp); { // render file names - float x = menu_x1 + 10, y = menu_y1 + char_height * 0.75f + 10; - TextRenderState text_render_state = {.min_x = menu_x1, .max_x = menu_x2, .min_y = menu_y1, .max_y = menu_y2}; + float x = menu_x1 + inner_padding, y = menu_y1 + line_buffer_height + inner_padding; + TextRenderState text_render_state = {.min_x = menu_x1, .max_x = menu_x2, .min_y = menu_y1, .max_y = menu_y2, .render = true}; gl_color_rgba(colors[COLOR_TEXT]); for (u32 i = 0; i < nfiles; ++i) { text_render_with_state(font, &text_render_state, files[i], x, y); @@ -50,6 +79,5 @@ static void menu_render(Ted *ted, Menu menu) { for (u32 i = 0; i < nfiles; ++i) free(files[i]); free(files); } - } } @@ -48,6 +48,29 @@ String32 str32_from_utf8(char const *utf8) { return string; } +// returns a null-terminated UTF-8 string +// the string returned should be free'd +// this will return NULL on failure +static char *str32_to_utf8_cstr(String32 s) { + char *utf8 = calloc(4 * s.len + 1, 1); // each codepoint takes up at most 4 bytes in UTF-8, + we need a terminating null byte + if (utf8) { + char *p = utf8; + mbstate_t mbstate; memset(&mbstate, 0, sizeof mbstate); + for (size_t i = 0; i < s.len; ++i) { + size_t bytes = c32rtomb(p, s.str[i], &mbstate); + if (bytes == (size_t)-1) { + // invalid UTF-32 character + free(utf8); + return NULL; + } else { + p += bytes; + } + } + *p = '\0'; + } + return utf8; +} + // returns the index of the given character in the string, or the length of the string if it's not found. size_t str32chr(String32 s, char32_t c) { for (size_t i = 0; i < s.len; ++i) { @@ -65,3 +88,21 @@ size_t str32_count_char(String32 s, char32_t c) { } return total; } + +// returns number of characters deleted from s +size_t str32_remove_all_instances_of_char(String32 *s, char32_t c) { + bool increment = true; + char32_t *str = s->str; + size_t ndeleted = 0; + for (size_t i = 0; i < s->len; i += increment, increment = true) { + if (str[i] == c) { + --s->len; + if (i < s->len) { + str[i] = str[i+1]; + } + ++ndeleted; + increment = false; + } + } + return ndeleted; +} @@ -80,14 +80,5 @@ static void ted_load_font(Ted *ted) { } } -static void ted_menu_open(Ted *ted, Menu menu) { - ted->menu = menu; - ted->prev_active_buffer = ted->active_buffer; - ted->active_buffer = NULL; -} - -static void ted_menu_close(Ted *ted, bool restore_prev_active_buffer) { - ted->menu = MENU_NONE; - if (restore_prev_active_buffer) ted->active_buffer = ted->prev_active_buffer; - ted->prev_active_buffer = NULL; -} +static void menu_open(Ted *ted, Menu menu); +static void menu_close(Ted *ted, bool restore_prev_active_buffer); @@ -61,6 +61,7 @@ Ctrl++ = 3 :increase-text-size Ctrl+- = 3 :decrease-text-size Escape = :escape +Return = :submit-line-buffer [colors] border = #a77 @@ -1,3 +1,5 @@ +#define TED_PATH_MAX 256 + #define TEXT_SIZE_MIN 6 #define TEXT_SIZE_MAX 70 @@ -22,7 +24,7 @@ typedef struct { #define KEY_MODIFIER_ALT (1<<KEY_MODIFIER_ALT_BIT) // ctrl+alt+c is encoded as SDL_SCANCODE_C << 3 | KEY_MODIFIER_CTRL | KEY_MODIFIER_ALT -typedef struct { +typedef struct KeyAction { u32 line_number; // config line number where this was set Command command; // this will be 0 (COMMAND_UNKNOWN) if there's no action for the key i64 argument; @@ -50,11 +52,12 @@ typedef struct BufferEdit { } BufferEdit; typedef struct { - char const *filename; + char *filename; // NULL if this buffer doesn't correspond to a file (e.g. line buffers) struct Ted *ted; // we keep a back-pointer to the ted instance so we don't have to pass it in to every buffer function double scroll_x, scroll_y; // number of characters scrolled in the x/y direction BufferPos cursor_pos; BufferPos selection_pos; // if selection is true, the text between selection_pos and cursor_pos is selected. + bool is_line_buffer; // "line buffers" are buffers which can only have one line of text (used for inputs) bool selection; bool store_undo_events; // set to false to disable undo events float x1, y1, x2, y2; @@ -80,6 +83,8 @@ typedef struct Ted { Settings settings; float window_width, window_height; Menu menu; + TextBuffer line_buffer; // general-purpose line buffer for inputs -- used for menus + TextBuffer main_buffer; KeyAction key_actions[KEY_COMBO_COUNT]; char error[256]; } Ted; @@ -46,6 +46,15 @@ static bool streq(char const *a, char const *b) { return strcmp(a, b) == 0; } +// duplicates a null-terminated string. the returned string should be passed to free() +static char *str_dup(char const *src) { + size_t len = strlen(src); + char *ret = malloc(len + 1); + if (ret) + memcpy(ret, src, len + 1); + return ret; +} + // 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. |