summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile17
-rw-r--r--Untitled4
-rw-r--r--buffer.c25
-rw-r--r--config.c17
-rw-r--r--filesystem.c19
-rw-r--r--main.c156
-rw-r--r--test.txt5
-rw-r--r--util.c105
8 files changed, 303 insertions, 45 deletions
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 <sys/types.h>
+#include <sys/stat.h>
+#if __unix__
+#include <unistd.h>
+#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 <GL/gl.h>
#include <locale.h>
#include <wctype.h>
+#if _WIN32
+#include <shellapi.h>
+#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;
+}
+