diff --git a/.assets/tree.example.png b/.assets/tree.example.png
new file mode 100644
index 0000000..a54a85d
Binary files /dev/null and b/.assets/tree.example.png differ
diff --git a/.assets/tree_showhidden.example.png b/.assets/tree_showhidden.example.png
new file mode 100644
index 0000000..2cefde3
Binary files /dev/null and b/.assets/tree_showhidden.example.png differ
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..e068d77
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,31 @@
+BasedOnStyle : LLVM
+AccessModifierOffset : -1
+AlignAfterOpenBracket : AlwaysBreak
+AlignConsecutiveAssignments : true
+AlignConsecutiveDeclarations : false
+AlignConsecutiveMacros : true
+AlignEscapedNewlines : Left
+AlignOperands : true
+AlignTrailingComments : true
+AllowAllArgumentsOnNextLine : true
+AllowAllConstructorInitializersOnNextLine : true
+AllowAllParametersOfDeclarationOnNextLine : true
+AllowShortBlocksOnASingleLine : true
+AllowShortCaseLabelsOnASingleLine : true
+AllowShortFunctionsOnASingleLine : false
+AllowShortIfStatementsOnASingleLine : Never
+AllowShortLambdasOnASingleLine : All
+AlwaysBreakAfterReturnType : All
+AlwaysBreakAfterDefinitionReturnType : All
+AlwaysBreakTemplateDeclarations : Yes
+KeepEmptyLinesAtTheStartOfBlocks : false
+IndentWrappedFunctionNames : false
+UseTab : Never
+IndentWidth : 2
+TabWidth : 2
+ContinuationIndentWidth : 4
+BinPackArguments : true
+BinPackParameters : true
+ColumnLimit : 80
+Language : Cpp
+Standard : Auto
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..3b83704
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,84 @@
+# - - - - - - - - - - - - - configuration - - - - - - - - - - - - - #
+
+BIN = treee
+PREFIX = /usr/local
+
+LIBS = -lncurses
+
+EXTDIR = ext
+INCDIR = include
+SRCDIR = src
+
+BINDIR = .bin
+OBJDIR = .obj
+DEPDIR = .dep
+
+SRC = disp.c \
+ file.c \
+ main.c
+
+CFLAGS = -std=gnu99 \
+ -g \
+ -pedantic \
+ -Wall \
+ -I/usr/include \
+ -I${INCDIR} \
+ -I${EXTDIR}
+LDFLAGS = ${LIBS}
+
+# - - - - - - - - - - - - - - - phony - - - - - - - - - - - - - - - - #
+
+all: ${BINDIR}/${BIN}
+
+run: ${BINDIR}/${BIN}
+ ./${BINDIR}/${BIN}
+
+install: ${BINDIR}/${BIN}
+ cp $< ${PREFIX}/bin
+
+uninstall:
+ rm -f ${PREFIX}/bin/${BIN}
+
+clean:
+ rm -rf ./${BINDIR} ./${OBJDIR} ./${DEPDIR}
+
+.PHONY: all run clean
+
+# - - - - - - - - - - - - - - - objects - - - - - - - - - - - - - - - #
+
+vpath %.c ${SRCDIR}
+
+OBJ = $(patsubst %.c,${OBJDIR}/%.o,$(SRC))
+
+${OBJ}: | ${OBJDIR}
+
+${OBJDIR}:
+ @mkdir -p $@
+
+${OBJDIR}/%.o: %.c
+ ${CC} ${CFLAGS} -c $< -o $@
+
+# - - - - - - - - - - - - - - - binary - - - - - - - - - - - - - - - #
+
+${BINDIR}:
+ @mkdir -p $@
+
+${BINDIR}/${BIN}: ${BINDIR} ${OBJ} ${INC}
+ ${CC} -o $@ ${OBJ} ${LDFLAGS}
+ @chmod 755 $@
+
+# - - - - - - - - - - - - - - dependencies - - - - - - - - - - - - - - #
+
+$(patsubst %.c,${DEPDIR}/%.d,$(SRC)): | ${DEPDIR}
+
+${DEPDIR}:
+ @mkdir -p $@
+
+${DEPDIR}/%.d: %.c
+ @${CC} ${CFLAGS} \
+ -MF"$@" -MG -M -MP \
+ -MT"$@" \
+ -MT"$(patsubst %.c,${DEPDIR}/%.o,${<})" \
+ "$<"
+
+include $(patsubst %.c,${DEPDIR}/%.d,$(SRC))
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e70e888
--- /dev/null
+++ b/README.md
@@ -0,0 +1,54 @@
+# treee
+
+treee is an interactive GNU/Linux `tree` clone.
+
+_This project is in a very early stage of development, but basic features are complete._
+
+## Purpose
+
+I have found `tree` to be a really great addition to development.
+
+This program is similar to `tree` but is built with ncurses;
+the working directory is scanned every second and updates the display.
+
+## Example
+
+![example](./.assets/tree.example.png)
+![showhidden.example](./.assets/tree_showhidden.example.png)
+
+## Usage
+
+```bash
+git clone https://github.com/jpcx/treee
+cd treee
+make
+sudo make install
+
+treee
+
+# for other directories:
+treee ../another_dir
+```
+
+## Controls
+
+`h`, `j`, `k`, `l` for motion
+
+_[hold shift for fast motion]_
+
+press `.` to toggle hidden file display
+
+pres `q` to exit
+
+## TODO
+
+- color different file types differently
+- add `/` control for search
+- add more command parameters
+ - enable/disable colors
+ - exclude matching
+- display more directory information
+
+## Contribution
+
+Contribution is welcome! Again, this is in an early stage of development.
diff --git a/ext/str.h b/ext/str.h
new file mode 100644
index 0000000..a62f582
--- /dev/null
+++ b/ext/str.h
@@ -0,0 +1,721 @@
+#ifndef STR_STR_H_INCLUDED
+#define STR_STR_H_INCLUDED
+/* /////////////////////////////////////////////////////////////////////////////
+// ___
+// ,--.'|_ str: C string management header [1.0.0]
+// | | :,' __ ,-. Copyright (C) 2020 Justin Collier
+// .--.--. : : ' : ,' ,'/ /|
+// / / '.;__,' / ' | |' | - - - - - - - - - - - - - - - - - - -
+// | : /`./| | | | | ,'
+// | : ;_ :__,'| : ' : / This program is free software: you can
+// \ \ `. ' : |__ | | ' redistribute it and/or modify it under the
+// `----. \| | '.'|; : | terms of the GNU General Public License
+// / /`--' /; : ;| , ; as published by the Free Software Foundation,
+// '--'. / | , / ---' either version 3 of the License, or (at your
+// `--'---' ---`-' option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the internalied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+// /
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . ///
+///////////////////////////////////////////////////////////////////////////// */
+
+/*
+ Synopsis
+ --------
+
+note: all functions are static inline
+
+ - - - ~ ~ construction ~ ~ - - -
+
+str str_alloc (size_t cap) : create a str with capacity cap
+str str_dup (const str s) : duplicate str storage (alloc)
+str str_new (const char *s) : record length, allocate, copy
+str str_sub (const char *s, size_t len) : copy up to len chars from s
+
+ - - - ~ ~ properties ~ ~ - - -
+
+int str_avail (const str s, size_t cap) : true if capacity is available
+size_t str_cap (const str s) : retrieve the capacity
+char * str_end (const str s) : pointer to the null terminator
+size_t str_len (const str s) : retrieve the length
+void * str_mbegin (str s) : ptr to allocated memory begin
+void * str_mend (str s) : ptr to allocated memory end
+size_t str_msize (const str s) : size of allocated memory
+str str_mstr (void *m) : str pointer from mbegin
+
+ - - - ~ ~ manipulation ~ ~ - - -
+
+// concatenate //
+void str_append (str *a, const char *b) : append to a
+void str_append_ (str *a, const str b)
+void str_prepend (str *b, const char *a) : prepend to b
+void str_prepend_ (str *b, const str a)
+
+// transform //
+void str_emplace (str *s, const char *ins, : overwrite at idx
+ size_t idx)
+void str_emplace_ (str *s, const str ins,
+ size_t idx)
+void str_insert (str *s, const char *ins, : insert before s[idx]
+ size_t idx)
+void str_insert_ (str *s, const str ins,
+ size_t idx)
+
+// format //
+void str_cpad (str *s, size_t len) : center pad to reach len
+void str_lpad (str *s, size_t len) : left pad to reach len
+void str_rpad (str *s, size_t len) : right pad to reach len
+void str_trim (str *s) : trim leading and trailing
+ whitespace [no realloc]
+
+// manage //
+void str_clear (str *s) : zero len, term [no realloc]
+void str_fit (str *s, size_t min_cap) : resize if capacity < min_cap
+void str_grow (str *s, size_t delta) : grow string capacity by delta
+void str_realloc (str *s, size_t cap) : resize string [null if needed]
+void str_shrink (str *s, size_t delta) : shrink cap [null if needed]
+void str_shrinkfit (str *s) : shrink to len if cap > len
+
+ - - - ~ ~ destruction ~ ~ - - -
+
+void str_free (str *s) : free owned string, nullify ptr
+
+*/
+
+#ifdef __cplusplus
+extern "C" {
+#include
+#include
+#include
+#else
+#include
+#include
+#include
+#endif
+
+/*.----------------------------------------------------------------------------,
+ / detail macros */
+
+/* config */
+
+/* */ /* clang-format off */
+
+#ifndef STR_CONFIG_NAMESPACE
+/** defines the prefix of all defined C symbols [default `str`]
+ * if necessary, `#define STR_CONFIG_NAMESPACE [name]` before inclusion */
+# define STR_CONFIG_NAMESPACE str
+#else
+# define STR_DETAIL_USING_CUSTOM_NAMESPACE
+#endif
+
+#ifndef STR_CONFIG_MALLOC
+# define STR_CONFIG_MALLOC malloc
+#else
+# define STR_DETAIL_USING_CUSTOM_MALLOC
+#endif
+
+#ifndef STR_CONFIG_FREE
+# define STR_CONFIG_FREE free
+#else
+# define STR_DETAIL_USING_CUSTOM_FREE
+#endif
+
+/* preprocessor */
+
+/** Cat. */
+#define STR_DETAIL_PP_CAT(a, b) STR_DETAIL_PP_CAT_X(a, b)
+#define STR_DETAIL_PP_CAT_X(a, b) a##b
+
+/* project setup */
+
+/** refers to a namespaced library function name [eg `str_free`] */
+#define STR_DETAIL_NS_FN(name) \
+ STR_DETAIL_PP_CAT(STR_CONFIG_NAMESPACE, STR_DETAIL_PP_CAT(_, name))
+
+#define STR_VERSION_MAJOR 1
+#define STR_VERSION_MINOR 0
+#define STR_VERSION_PATCH 0
+
+/* thanks to cxong/tinydir for this inline definition logic */
+#ifdef _MSC_VER
+# define STR_FUNCTION static __inline
+#elif !defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L
+# define STR_FUNCTION static __inline__
+#else
+# define STR_FUNCTION static inline
+#endif
+
+/** temporary definitions for project readability; undefined at end of file */
+#ifdef STR_DETAIL_USING_CUSTOM_NAMESPACE
+# define str STR_CONFIG_NAMESPACE
+# define str_alloc STR_DETAIL_NS_FN(alloc)
+# define str_dup STR_DETAIL_NS_FN(dup)
+# define str_new STR_DETAIL_NS_FN(new)
+# define str_sub STR_DETAIL_NS_FN(sub)
+# define str_avail STR_DETAIL_NS_FN(avail)
+# define str_cap STR_DETAIL_NS_FN(cap)
+# define str_end STR_DETAIL_NS_FN(end)
+# define str_len STR_DETAIL_NS_FN(len)
+# define str_mbegin STR_DETAIL_NS_FN(mbegin)
+# define str_mend STR_DETAIL_NS_FN(mend)
+# define str_msize STR_DETAIL_NS_FN(msize)
+# define str_mstr STR_DETAIL_NS_FN(mstr)
+# define str_append STR_DETAIL_NS_FN(append)
+# define str_append_ STR_DETAIL_NS_FN(append_)
+# define str_prepend STR_DETAIL_NS_FN(prepend)
+# define str_prepend_ STR_DETAIL_NS_FN(prepend_)
+# define str_emplace STR_DETAIL_NS_FN(emplace)
+# define str_emplace_ STR_DETAIL_NS_FN(emplace_)
+# define str_insert STR_DETAIL_NS_FN(insert)
+# define str_insert_ STR_DETAIL_NS_FN(insert_)
+# define str_cpad STR_DETAIL_NS_FN(cpad)
+# define str_lpad STR_DETAIL_NS_FN(lpad)
+# define str_rpad STR_DETAIL_NS_FN(rpad)
+# define str_trim STR_DETAIL_NS_FN(trim)
+# define str_clear STR_DETAIL_NS_FN(clear)
+# define str_fit STR_DETAIL_NS_FN(fit)
+# define str_grow STR_DETAIL_NS_FN(grow)
+# define str_realloc STR_DETAIL_NS_FN(realloc)
+# define str_shrink STR_DETAIL_NS_FN(shrink)
+# define str_shrinkfit STR_DETAIL_NS_FN(shrinkfit)
+# define str_free STR_DETAIL_NS_FN(free)
+#endif
+
+/* */ /* clang-format on */
+
+/* implementation helpers */
+
+/** defines the size of the memory block given a capacity */
+#define STR_DETAIL_MEMORY_SIZE(cap) \
+ sizeof(size_t) * 2 + sizeof(char) * (cap + 1)
+
+/** shifts a char* to the left by n */
+#define STR_DETAIL_SHIFT_LEFT(cstr, len, n) \
+ { \
+ size_t i_; \
+ for (i_ = 0; i_ < len; ++i_) \
+ (cstr)[i_ - n] = (cstr)[i_]; \
+ }
+
+/** shifts a char* to the right by n */
+#define STR_DETAIL_SHIFT_RIGHT(cstr, len, n) \
+ { \
+ size_t i_; \
+ for (i_ = len + 1; i_ > 0; --i_) \
+ (cstr)[(i_ - 1) + n] = (cstr)[i_ - 1]; \
+ }
+
+/** assigns len to its memory location */
+#define STR_DETAIL_SET_LEN(str, len) *(((size_t *)(str)) - 1) = len
+
+/** assigns cap to its memory location */
+#define STR_DETAIL_SET_CAP(str, cap) *(((size_t *)(str)) - 2) = cap
+
+/*.----------------------------------------------------------------------------,
+ / type alias */
+
+typedef char *str;
+
+/*.----------------------------------------------------------------------------,
+ / declarations */
+
+/* construction */
+
+/** create a str with capacity cap */
+STR_FUNCTION str
+str_alloc(size_t cap);
+/** duplicate str storage (alloc) */
+STR_FUNCTION str
+str_dup(const str s);
+/** record length, allocate, copy */
+STR_FUNCTION str
+str_new(const char *s);
+/** copy up to len chars from s */
+STR_FUNCTION str
+str_sub(const char *s, size_t len);
+
+/* properties */
+
+/** true if capacity is available */
+STR_FUNCTION int
+str_avail(const str s, size_t cap);
+/** retrieve the capacity */
+STR_FUNCTION size_t
+str_cap(const str s);
+/** pointer to the null terminator */
+STR_FUNCTION char *
+str_end(const str s);
+/** retrieve the length */
+STR_FUNCTION size_t
+str_len(const str s);
+/** ptr to allocated memory begin */
+STR_FUNCTION void *
+str_mbegin(str s);
+/** ptr to allocated memory end */
+STR_FUNCTION void *
+str_mend(str s);
+/** size of allocated memory */
+STR_FUNCTION size_t
+str_msize(const str s);
+/** str pointer from mbegin */
+STR_FUNCTION str
+str_mstr(void *m);
+
+/* manipulation */
+
+/** append chars to a */
+STR_FUNCTION void
+str_append(str *a, const char *b);
+/** append str to a */
+STR_FUNCTION void
+str_append_(str *a, const str b);
+/** prepend chars to b */
+STR_FUNCTION void
+str_prepend(str *b, const char *a);
+/** prepend str to b */
+STR_FUNCTION void
+str_prepend_(str *b, const str a);
+
+/** overwrite chars in a string */
+STR_FUNCTION void
+str_emplace(str *a, const char *b, size_t idx);
+/** overwrite chars in a string */
+STR_FUNCTION void
+str_emplace_(str *a, const str b, size_t idx);
+/** insert chars before s[idx] */
+STR_FUNCTION void
+str_insert(str *s, const char *ins, size_t idx);
+/** insert str before s[idx] */
+STR_FUNCTION void
+str_insert_(str *s, const str ins, size_t idx);
+
+/** center pad to reach len */
+STR_FUNCTION void
+str_cpad(str *s, size_t len);
+/** left pad to reach len */
+STR_FUNCTION void
+str_lpad(str *s, size_t len);
+/** right pad to reach len */
+STR_FUNCTION void
+str_rpad(str *s, size_t len);
+/** trim leading and trailing whitespace */
+STR_FUNCTION void
+str_trim(str *s);
+
+/** zero len, term [no realloc] */
+STR_FUNCTION void
+str_clear(str *a);
+/** resize if capacity < min_cap */
+STR_FUNCTION void
+str_fit(str *a, size_t min_cap);
+/** grow string capacity by delta */
+STR_FUNCTION void
+str_grow(str *a, size_t delta);
+/** resize string [null if needed] */
+STR_FUNCTION void
+str_realloc(str *s, size_t cap);
+/** shrink cap [null if needed] */
+STR_FUNCTION void
+str_shrink(str *a, size_t delta);
+/** resize to len */
+STR_FUNCTION void
+str_shrinkfit(str *s);
+
+/* destruction */
+
+/** free owned string, nullify ptr */
+STR_FUNCTION void
+str_free(str *s);
+
+/*.----------------------------------------------------------------------------,
+ / definitions */
+
+/* construction */
+
+/** create a str with capacity cap */
+STR_FUNCTION str
+str_alloc(size_t cap) {
+ void *o = STR_CONFIG_MALLOC(STR_DETAIL_MEMORY_SIZE(cap));
+ if (o == NULL)
+ return NULL;
+ str s = str_mstr(o);
+ STR_DETAIL_SET_CAP(s, cap);
+ STR_DETAIL_SET_LEN(s, 0);
+ s[0] = '\0';
+ return s;
+}
+
+/** duplicate str storage (alloc) */
+STR_FUNCTION str
+str_dup(const str s) {
+ void *o = STR_CONFIG_MALLOC(str_msize(s));
+ if (o == NULL)
+ return NULL;
+ memcpy(o, str_mbegin(s), str_msize(s));
+ return str_mstr(o);
+}
+
+/** record length, allocate, copy */
+STR_FUNCTION str
+str_new(const char *s) {
+ size_t len = strlen(s);
+ str v = str_alloc(len);
+ if (v == NULL)
+ return NULL;
+ strcpy(v, s);
+ STR_DETAIL_SET_LEN(v, len);
+ return v;
+}
+
+/** copy up to len chars from s */
+STR_FUNCTION str
+str_sub(const char *s, size_t len) {
+ str v = str_alloc(len);
+ if (v == NULL)
+ return NULL;
+ size_t i;
+ for (i = 0; i < len && s[i] != '\0'; ++i)
+ v[i] = s[i];
+ v[i] = '\0';
+ STR_DETAIL_SET_LEN(v, i);
+ return v;
+}
+
+/* properties */
+
+/** true if capacity is available */
+STR_FUNCTION int
+str_avail(const str s, size_t cap) {
+ return str_cap(s) >= cap;
+}
+
+/** retrieve the capacity */
+STR_FUNCTION size_t
+str_cap(const str s) {
+ return *(((size_t *)s) - 2);
+}
+
+/** pointer to the null terminator */
+STR_FUNCTION char *
+str_end(const str s) {
+ return &s[str_len(s)];
+}
+
+/** retrieve the length */
+STR_FUNCTION size_t
+str_len(const str s) {
+ return *(((size_t *)s) - 1);
+}
+
+/** ptr to allocated memory begin */
+STR_FUNCTION void *
+str_mbegin(const str s) {
+ return (size_t *)s - 2;
+}
+
+/** ptr to allocated memory end */
+STR_FUNCTION void *
+str_mend(const str s) {
+ return s + str_cap(s);
+}
+
+/** size of allocated memory */
+STR_FUNCTION size_t
+str_msize(const str s) {
+ return STR_DETAIL_MEMORY_SIZE(str_cap(s));
+}
+
+/** str pointer from mbegin */
+STR_FUNCTION str
+str_mstr(void *m) {
+ return (str)((size_t *)(m) + 2);
+}
+
+/* manipulation */
+
+/** append chars to a */
+STR_FUNCTION void
+str_append(str *a, const char *b) {
+ size_t alen = str_len(*a);
+ size_t blen = strlen(b);
+ str_fit(a, alen + blen);
+ strcpy(&(*a)[alen], b);
+ STR_DETAIL_SET_LEN(*a, alen + blen);
+}
+
+/** append str to a */
+STR_FUNCTION void
+str_append_(str *a, const str b) {
+ size_t alen = str_len(*a);
+ size_t blen = str_len(b);
+ str_fit(a, alen + blen);
+ strcpy(&(*a)[alen], b);
+ STR_DETAIL_SET_LEN(*a, alen + blen);
+}
+
+/** prepend chars to b */
+STR_FUNCTION void
+str_prepend(str *b, const char *a) {
+ size_t blen = str_len(*b);
+ size_t alen = strlen(a);
+ str_fit(b, alen + blen);
+ STR_DETAIL_SHIFT_RIGHT(*b, blen, alen);
+ memcpy((*b), a, alen);
+ STR_DETAIL_SET_LEN(*b, alen + blen);
+}
+
+/** prepend str to b */
+STR_FUNCTION void
+str_prepend_(str *b, const str a) {
+ size_t blen = str_len(*b);
+ size_t alen = str_len(a);
+ str_fit(b, alen + blen);
+ STR_DETAIL_SHIFT_RIGHT(*b, blen, alen);
+ memcpy((*b), a, alen);
+ STR_DETAIL_SET_LEN(*b, alen + blen);
+}
+
+/** overwrite chars in a string */
+STR_FUNCTION void
+str_emplace(str *s, const char *ins, size_t idx) {
+ size_t inslen = strlen(ins);
+ str_fit(s, idx + inslen);
+ memcpy(&(*s)[idx], ins, inslen);
+ if (idx + inslen > str_len(*s)) {
+ (*s)[idx + inslen] = '\0';
+ STR_DETAIL_SET_LEN(*s, idx + inslen);
+ }
+}
+
+/** overwrite chars in a string */
+STR_FUNCTION void
+str_emplace_(str *s, const str ins, size_t idx) {
+ size_t inslen = str_len(ins);
+ str_fit(s, idx + inslen);
+ memcpy(&(*s)[idx], ins, inslen);
+ if (idx + inslen > str_len(*s)) {
+ (*s)[idx + inslen] = '\0';
+ STR_DETAIL_SET_LEN(*s, idx + inslen);
+ }
+}
+
+/** insert chars before s[idx] */
+STR_FUNCTION void
+str_insert(str *s, const char *ins, size_t idx) {
+ size_t slen = str_len(*s);
+ size_t inslen = strlen(ins);
+ str_fit(s, slen + inslen);
+ STR_DETAIL_SHIFT_RIGHT(&(*s)[idx], slen - idx, inslen);
+ memcpy(&(*s)[idx], ins, inslen);
+ STR_DETAIL_SET_LEN(*s, slen + inslen);
+}
+
+/** insert str before s[idx] */
+STR_FUNCTION void
+str_insert_(str *s, const str ins, size_t idx) {
+ size_t slen = str_len(*s);
+ size_t inslen = str_len(ins);
+ str_fit(s, slen + inslen);
+ STR_DETAIL_SHIFT_RIGHT(&(*s)[idx], slen - idx, inslen);
+ memcpy(&(*s)[idx], ins, inslen);
+ STR_DETAIL_SET_LEN(*s, slen + inslen);
+}
+
+/** center pad to reach len */
+STR_FUNCTION void
+str_cpad(str *s, size_t len) {
+ size_t slen = str_len(*s);
+ if (slen < len) {
+ str_fit(s, len);
+ size_t mid = (len - slen) / 2;
+ STR_DETAIL_SHIFT_RIGHT(*s, slen, mid);
+ memset(*s, ' ', mid);
+ memset(&(*s)[mid + slen], ' ', len - mid);
+ (*s)[len] = '\0';
+ STR_DETAIL_SET_LEN(*s, len);
+ }
+}
+
+/** left pad to reach len */
+STR_FUNCTION void
+str_lpad(str *s, size_t len) {
+ size_t slen = str_len(*s);
+ if (slen < len) {
+ str_fit(s, len);
+ STR_DETAIL_SHIFT_RIGHT(*s, slen, len - slen);
+ memset(*s, ' ', len - slen);
+ STR_DETAIL_SET_LEN(*s, len);
+ }
+}
+
+/** right pad to reach len */
+STR_FUNCTION void
+str_rpad(str *s, size_t len) {
+ size_t slen = str_len(*s);
+ if (slen < len) {
+ str_fit(s, len);
+ memset(&(*s)[slen], ' ', len - slen);
+ (*s)[len] = '\0';
+ STR_DETAIL_SET_LEN(*s, len);
+ }
+}
+
+/** trim leading and trailing whitespace */
+STR_FUNCTION void
+str_trim(str *s) {
+ size_t slen = str_len(*s);
+ size_t beg;
+ size_t end;
+ for (beg = 0; beg < slen && isspace((*s)[beg]); ++beg)
+ ;
+ for (end = slen; end > 0 && isspace((*s)[end - 1]); --end)
+ ;
+
+ if (beg > 0)
+ STR_DETAIL_SHIFT_LEFT(&(*s)[beg], end - beg, beg);
+
+ if (beg > 0 || end < slen) {
+ (*s)[end - beg] = '\0';
+ STR_DETAIL_SET_LEN(*s, end - beg);
+ }
+}
+
+/** zero len, term [no realloc] */
+STR_FUNCTION void
+str_clear(str *s) {
+ (*s)[0] = '\0';
+ STR_DETAIL_SET_LEN(*s, 0);
+}
+
+/** resize if capacity < min_cap */
+STR_FUNCTION void
+str_fit(str *a, size_t min_cap) {
+ if (str_cap(*a) < min_cap)
+ str_realloc(a, min_cap);
+}
+
+/** grow string capacity by delta */
+STR_FUNCTION void
+str_grow(str *s, size_t delta) {
+ str_realloc(s, str_cap(*s) + delta);
+}
+
+/** resize string [reallocates and null terminates] */
+STR_FUNCTION void
+str_realloc(str *s, size_t cap) {
+ void *v = STR_CONFIG_MALLOC(STR_DETAIL_MEMORY_SIZE(cap));
+
+ if (v == NULL)
+ return;
+
+ memcpy(v, str_mbegin(*s), STR_DETAIL_MEMORY_SIZE(cap));
+ str_free(s);
+
+ *s = str_mstr(v);
+ STR_DETAIL_SET_CAP(*s, cap);
+
+ if (cap < str_len(*s)) {
+ (*s)[cap] = '\0';
+ STR_DETAIL_SET_LEN(*s, cap);
+ }
+}
+
+/** shrink cap [null if needed] */
+STR_FUNCTION void
+str_shrink(str *s, size_t delta) {
+ str_realloc(s, str_cap(*s) - delta);
+}
+
+/** shrink to len if cap > len */
+STR_FUNCTION void
+str_shrinkfit(str *s) {
+ size_t slen = str_len(*s);
+ if (slen < str_cap(*s))
+ str_realloc(s, slen);
+}
+
+/* destruction */
+
+/** free owned string, nullify ptr */
+STR_FUNCTION void
+str_free(str *s) {
+ STR_CONFIG_FREE(str_mbegin(*s));
+ *s = NULL;
+}
+
+/* */ /* clang-format off */
+
+#undef STR_DETAIL_PP_CAT
+#undef STR_DETAIL_PP_CAT_X
+#undef STR_DETAIL_NS_FN
+#undef STR_FUNCTION
+
+#ifdef STR_DETAIL_USING_CUSTOM_NAMESPACE
+# undef str
+# undef str_alloc
+# undef str_dup
+# undef str_new
+# undef str_sub
+# undef str_avail
+# undef str_cap
+# undef str_end
+# undef str_len
+# undef str_mbegin
+# undef str_mend
+# undef str_msize
+# undef str_mstr
+# undef str_append
+# undef str_append_
+# undef str_prepend
+# undef str_prepend_
+# undef str_emplace
+# undef str_emplace_
+# undef str_insert
+# undef str_insert_
+# undef str_cpad
+# undef str_lpad
+# undef str_rpad
+# undef str_trim
+# undef str_clear
+# undef str_fit
+# undef str_grow
+# undef str_realloc
+# undef str_shrink
+# undef str_shrinkfit
+# undef str_free
+#endif
+
+#ifdef STR_DETAIL_USING_CUSTOM_NAMESPACE
+# undef STR_DETAIL_USING_CUSTOM_NAMESPACE
+#else
+# undef STR_CONFIG_NAMESPACE
+#endif
+
+#ifdef STR_DETAIL_USING_CUSTOM_MALLOC
+# undef STR_DETAIL_USING_CUSTOM_MALLOC
+#else
+# undef STR_CONFIG_MALLOC
+#endif
+
+#ifdef STR_DETAIL_USING_CUSTOM_FREE
+# undef STR_DETAIL_USING_CUSTOM_FREE
+#else
+# undef STR_CONFIG_FREE
+#endif
+
+#undef STR_DETAIL_MEMORY_SIZE
+#undef STR_DETAIL_SHIFT_RIGHT
+#undef STR_DETAIL_SHIFT_LEFT
+#undef STR_DETAIL_SET_LEN
+#undef STR_DETAIL_SET_CAP
+/* */ /* clang-format on */
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif
diff --git a/ext/tinydir.h b/ext/tinydir.h
new file mode 100644
index 0000000..e08eb84
--- /dev/null
+++ b/ext/tinydir.h
@@ -0,0 +1,831 @@
+/*
+Copyright (c) 2013-2019, tinydir authors:
+- Cong Xu
+- Lautis Sun
+- Baudouin Feildel
+- Andargor
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+#ifndef TINYDIR_H
+#define TINYDIR_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if ((defined _UNICODE) && !(defined UNICODE))
+#define UNICODE
+#endif
+
+#if ((defined UNICODE) && !(defined _UNICODE))
+#define _UNICODE
+#endif
+
+#include
+#include
+#include
+#ifdef _MSC_VER
+# ifndef WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+# endif
+# include
+# include
+# pragma warning(push)
+# pragma warning (disable : 4996)
+#else
+# include
+# include
+# include
+# include
+#endif
+#ifdef __MINGW32__
+# include
+#endif
+
+
+/* types */
+
+/* Windows UNICODE wide character support */
+#if defined _MSC_VER || defined __MINGW32__
+# define _tinydir_char_t TCHAR
+# define TINYDIR_STRING(s) _TEXT(s)
+# define _tinydir_strlen _tcslen
+# define _tinydir_strcpy _tcscpy
+# define _tinydir_strcat _tcscat
+# define _tinydir_strcmp _tcscmp
+# define _tinydir_strrchr _tcsrchr
+# define _tinydir_strncmp _tcsncmp
+#else
+# define _tinydir_char_t char
+# define TINYDIR_STRING(s) s
+# define _tinydir_strlen strlen
+# define _tinydir_strcpy strcpy
+# define _tinydir_strcat strcat
+# define _tinydir_strcmp strcmp
+# define _tinydir_strrchr strrchr
+# define _tinydir_strncmp strncmp
+#endif
+
+#if (defined _MSC_VER || defined __MINGW32__)
+# include
+# define _TINYDIR_PATH_MAX MAX_PATH
+#elif defined __linux__
+# include
+# ifdef PATH_MAX
+# define _TINYDIR_PATH_MAX PATH_MAX
+# endif
+#elif defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
+# include
+# if defined(BSD)
+# include
+# ifdef PATH_MAX
+# define _TINYDIR_PATH_MAX PATH_MAX
+# endif
+# endif
+#endif
+
+#ifndef _TINYDIR_PATH_MAX
+#define _TINYDIR_PATH_MAX 4096
+#endif
+
+#ifdef _MSC_VER
+/* extra chars for the "\\*" mask */
+# define _TINYDIR_PATH_EXTRA 2
+#else
+# define _TINYDIR_PATH_EXTRA 0
+#endif
+
+#define _TINYDIR_FILENAME_MAX 256
+
+#if (defined _MSC_VER || defined __MINGW32__)
+#define _TINYDIR_DRIVE_MAX 3
+#endif
+
+#ifdef _MSC_VER
+# define _TINYDIR_FUNC static __inline
+#elif !defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L
+# define _TINYDIR_FUNC static __inline__
+#else
+# define _TINYDIR_FUNC static inline
+#endif
+
+/* readdir_r usage; define TINYDIR_USE_READDIR_R to use it (if supported) */
+#ifdef TINYDIR_USE_READDIR_R
+
+/* readdir_r is a POSIX-only function, and may not be available under various
+ * environments/settings, e.g. MinGW. Use readdir fallback */
+#if _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _BSD_SOURCE || _SVID_SOURCE ||\
+ _POSIX_SOURCE
+# define _TINYDIR_HAS_READDIR_R
+#endif
+#if _POSIX_C_SOURCE >= 200112L
+# define _TINYDIR_HAS_FPATHCONF
+# include
+#endif
+#if _BSD_SOURCE || _SVID_SOURCE || \
+ (_POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700)
+# define _TINYDIR_HAS_DIRFD
+# include
+#endif
+#if defined _TINYDIR_HAS_FPATHCONF && defined _TINYDIR_HAS_DIRFD &&\
+ defined _PC_NAME_MAX
+# define _TINYDIR_USE_FPATHCONF
+#endif
+#if defined __MINGW32__ || !defined _TINYDIR_HAS_READDIR_R ||\
+ !(defined _TINYDIR_USE_FPATHCONF || defined NAME_MAX)
+# define _TINYDIR_USE_READDIR
+#endif
+
+/* Use readdir by default */
+#else
+# define _TINYDIR_USE_READDIR
+#endif
+
+/* MINGW32 has two versions of dirent, ASCII and UNICODE*/
+#ifndef _MSC_VER
+#if (defined __MINGW32__) && (defined _UNICODE)
+#define _TINYDIR_DIR _WDIR
+#define _tinydir_dirent _wdirent
+#define _tinydir_opendir _wopendir
+#define _tinydir_readdir _wreaddir
+#define _tinydir_closedir _wclosedir
+#else
+#define _TINYDIR_DIR DIR
+#define _tinydir_dirent dirent
+#define _tinydir_opendir opendir
+#define _tinydir_readdir readdir
+#define _tinydir_closedir closedir
+#endif
+#endif
+
+/* Allow user to use a custom allocator by defining _TINYDIR_MALLOC and _TINYDIR_FREE. */
+#if defined(_TINYDIR_MALLOC) && defined(_TINYDIR_FREE)
+#elif !defined(_TINYDIR_MALLOC) && !defined(_TINYDIR_FREE)
+#else
+#error "Either define both alloc and free or none of them!"
+#endif
+
+#if !defined(_TINYDIR_MALLOC)
+ #define _TINYDIR_MALLOC(_size) malloc(_size)
+ #define _TINYDIR_FREE(_ptr) free(_ptr)
+#endif /* !defined(_TINYDIR_MALLOC) */
+
+typedef struct tinydir_file
+{
+ _tinydir_char_t path[_TINYDIR_PATH_MAX];
+ _tinydir_char_t name[_TINYDIR_FILENAME_MAX];
+ _tinydir_char_t *extension;
+ int is_dir;
+ int is_reg;
+
+#ifndef _MSC_VER
+#ifdef __MINGW32__
+ struct _stat _s;
+#else
+ struct stat _s;
+#endif
+#endif
+} tinydir_file;
+
+typedef struct tinydir_dir
+{
+ _tinydir_char_t path[_TINYDIR_PATH_MAX];
+ int has_next;
+ size_t n_files;
+
+ tinydir_file *_files;
+#ifdef _MSC_VER
+ HANDLE _h;
+ WIN32_FIND_DATA _f;
+#else
+ _TINYDIR_DIR *_d;
+ struct _tinydir_dirent *_e;
+#ifndef _TINYDIR_USE_READDIR
+ struct _tinydir_dirent *_ep;
+#endif
+#endif
+} tinydir_dir;
+
+
+/* declarations */
+
+_TINYDIR_FUNC
+int tinydir_open(tinydir_dir *dir, const _tinydir_char_t *path);
+_TINYDIR_FUNC
+int tinydir_open_sorted(tinydir_dir *dir, const _tinydir_char_t *path);
+_TINYDIR_FUNC
+void tinydir_close(tinydir_dir *dir);
+
+_TINYDIR_FUNC
+int tinydir_next(tinydir_dir *dir);
+_TINYDIR_FUNC
+int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file);
+_TINYDIR_FUNC
+int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i);
+_TINYDIR_FUNC
+int tinydir_open_subdir_n(tinydir_dir *dir, size_t i);
+
+_TINYDIR_FUNC
+int tinydir_file_open(tinydir_file *file, const _tinydir_char_t *path);
+_TINYDIR_FUNC
+void _tinydir_get_ext(tinydir_file *file);
+_TINYDIR_FUNC
+int _tinydir_file_cmp(const void *a, const void *b);
+#ifndef _MSC_VER
+#ifndef _TINYDIR_USE_READDIR
+_TINYDIR_FUNC
+size_t _tinydir_dirent_buf_size(_TINYDIR_DIR *dirp);
+#endif
+#endif
+
+
+/* definitions*/
+
+_TINYDIR_FUNC
+int tinydir_open(tinydir_dir *dir, const _tinydir_char_t *path)
+{
+#ifndef _MSC_VER
+#ifndef _TINYDIR_USE_READDIR
+ int error;
+ int size; /* using int size */
+#endif
+#else
+ _tinydir_char_t path_buf[_TINYDIR_PATH_MAX];
+#endif
+ _tinydir_char_t *pathp;
+
+ if (dir == NULL || path == NULL || _tinydir_strlen(path) == 0)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+ if (_tinydir_strlen(path) + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX)
+ {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ /* initialise dir */
+ dir->_files = NULL;
+#ifdef _MSC_VER
+ dir->_h = INVALID_HANDLE_VALUE;
+#else
+ dir->_d = NULL;
+#ifndef _TINYDIR_USE_READDIR
+ dir->_ep = NULL;
+#endif
+#endif
+ tinydir_close(dir);
+
+ _tinydir_strcpy(dir->path, path);
+ /* Remove trailing slashes */
+ pathp = &dir->path[_tinydir_strlen(dir->path) - 1];
+ while (pathp != dir->path && (*pathp == TINYDIR_STRING('\\') || *pathp == TINYDIR_STRING('/')))
+ {
+ *pathp = TINYDIR_STRING('\0');
+ pathp++;
+ }
+#ifdef _MSC_VER
+ _tinydir_strcpy(path_buf, dir->path);
+ _tinydir_strcat(path_buf, TINYDIR_STRING("\\*"));
+#if (defined WINAPI_FAMILY) && (WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP)
+ dir->_h = FindFirstFileEx(path_buf, FindExInfoStandard, &dir->_f, FindExSearchNameMatch, NULL, 0);
+#else
+ dir->_h = FindFirstFile(path_buf, &dir->_f);
+#endif
+ if (dir->_h == INVALID_HANDLE_VALUE)
+ {
+ errno = ENOENT;
+#else
+ dir->_d = _tinydir_opendir(path);
+ if (dir->_d == NULL)
+ {
+#endif
+ goto bail;
+ }
+
+ /* read first file */
+ dir->has_next = 1;
+#ifndef _MSC_VER
+#ifdef _TINYDIR_USE_READDIR
+ dir->_e = _tinydir_readdir(dir->_d);
+#else
+ /* allocate dirent buffer for readdir_r */
+ size = _tinydir_dirent_buf_size(dir->_d); /* conversion to int */
+ if (size == -1) return -1;
+ dir->_ep = (struct _tinydir_dirent*)_TINYDIR_MALLOC(size);
+ if (dir->_ep == NULL) return -1;
+
+ error = readdir_r(dir->_d, dir->_ep, &dir->_e);
+ if (error != 0) return -1;
+#endif
+ if (dir->_e == NULL)
+ {
+ dir->has_next = 0;
+ }
+#endif
+
+ return 0;
+
+bail:
+ tinydir_close(dir);
+ return -1;
+}
+
+_TINYDIR_FUNC
+int tinydir_open_sorted(tinydir_dir *dir, const _tinydir_char_t *path)
+{
+ /* Count the number of files first, to pre-allocate the files array */
+ size_t n_files = 0;
+ if (tinydir_open(dir, path) == -1)
+ {
+ return -1;
+ }
+ while (dir->has_next)
+ {
+ n_files++;
+ if (tinydir_next(dir) == -1)
+ {
+ goto bail;
+ }
+ }
+ tinydir_close(dir);
+
+ if (n_files == 0 || tinydir_open(dir, path) == -1)
+ {
+ return -1;
+ }
+
+ dir->n_files = 0;
+ dir->_files = (tinydir_file *)_TINYDIR_MALLOC(sizeof *dir->_files * n_files);
+ if (dir->_files == NULL)
+ {
+ goto bail;
+ }
+ while (dir->has_next)
+ {
+ tinydir_file *p_file;
+ dir->n_files++;
+
+ p_file = &dir->_files[dir->n_files - 1];
+ if (tinydir_readfile(dir, p_file) == -1)
+ {
+ goto bail;
+ }
+
+ if (tinydir_next(dir) == -1)
+ {
+ goto bail;
+ }
+
+ /* Just in case the number of files has changed between the first and
+ second reads, terminate without writing into unallocated memory */
+ if (dir->n_files == n_files)
+ {
+ break;
+ }
+ }
+
+ qsort(dir->_files, dir->n_files, sizeof(tinydir_file), _tinydir_file_cmp);
+
+ return 0;
+
+bail:
+ tinydir_close(dir);
+ return -1;
+}
+
+_TINYDIR_FUNC
+void tinydir_close(tinydir_dir *dir)
+{
+ if (dir == NULL)
+ {
+ return;
+ }
+
+ memset(dir->path, 0, sizeof(dir->path));
+ dir->has_next = 0;
+ dir->n_files = 0;
+ _TINYDIR_FREE(dir->_files);
+ dir->_files = NULL;
+#ifdef _MSC_VER
+ if (dir->_h != INVALID_HANDLE_VALUE)
+ {
+ FindClose(dir->_h);
+ }
+ dir->_h = INVALID_HANDLE_VALUE;
+#else
+ if (dir->_d)
+ {
+ _tinydir_closedir(dir->_d);
+ }
+ dir->_d = NULL;
+ dir->_e = NULL;
+#ifndef _TINYDIR_USE_READDIR
+ _TINYDIR_FREE(dir->_ep);
+ dir->_ep = NULL;
+#endif
+#endif
+}
+
+_TINYDIR_FUNC
+int tinydir_next(tinydir_dir *dir)
+{
+ if (dir == NULL)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+ if (!dir->has_next)
+ {
+ errno = ENOENT;
+ return -1;
+ }
+
+#ifdef _MSC_VER
+ if (FindNextFile(dir->_h, &dir->_f) == 0)
+#else
+#ifdef _TINYDIR_USE_READDIR
+ dir->_e = _tinydir_readdir(dir->_d);
+#else
+ if (dir->_ep == NULL)
+ {
+ return -1;
+ }
+ if (readdir_r(dir->_d, dir->_ep, &dir->_e) != 0)
+ {
+ return -1;
+ }
+#endif
+ if (dir->_e == NULL)
+#endif
+ {
+ dir->has_next = 0;
+#ifdef _MSC_VER
+ if (GetLastError() != ERROR_SUCCESS &&
+ GetLastError() != ERROR_NO_MORE_FILES)
+ {
+ tinydir_close(dir);
+ errno = EIO;
+ return -1;
+ }
+#endif
+ }
+
+ return 0;
+}
+
+_TINYDIR_FUNC
+int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file)
+{
+ const _tinydir_char_t *filename;
+ if (dir == NULL || file == NULL)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+#ifdef _MSC_VER
+ if (dir->_h == INVALID_HANDLE_VALUE)
+#else
+ if (dir->_e == NULL)
+#endif
+ {
+ errno = ENOENT;
+ return -1;
+ }
+ filename =
+#ifdef _MSC_VER
+ dir->_f.cFileName;
+#else
+ dir->_e->d_name;
+#endif
+ if (_tinydir_strlen(dir->path) +
+ _tinydir_strlen(filename) + 1 + _TINYDIR_PATH_EXTRA >=
+ _TINYDIR_PATH_MAX)
+ {
+ /* the path for the file will be too long */
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+ if (_tinydir_strlen(filename) >= _TINYDIR_FILENAME_MAX)
+ {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ _tinydir_strcpy(file->path, dir->path);
+ if (_tinydir_strcmp(dir->path, TINYDIR_STRING("/")) != 0)
+ _tinydir_strcat(file->path, TINYDIR_STRING("/"));
+ _tinydir_strcpy(file->name, filename);
+ _tinydir_strcat(file->path, filename);
+#ifndef _MSC_VER
+#ifdef __MINGW32__
+ if (_tstat(
+#elif (defined _BSD_SOURCE) || (defined _DEFAULT_SOURCE) \
+ || ((defined _XOPEN_SOURCE) && (_XOPEN_SOURCE >= 500)) \
+ || ((defined _POSIX_C_SOURCE) && (_POSIX_C_SOURCE >= 200112L))
+ if (lstat(
+#else
+ if (stat(
+#endif
+ file->path, &file->_s) == -1)
+ {
+ return -1;
+ }
+#endif
+ _tinydir_get_ext(file);
+
+ file->is_dir =
+#ifdef _MSC_VER
+ !!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
+#else
+ S_ISDIR(file->_s.st_mode);
+#endif
+ file->is_reg =
+#ifdef _MSC_VER
+ !!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NORMAL) ||
+ (
+ !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DEVICE) &&
+ !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
+ !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) &&
+#ifdef FILE_ATTRIBUTE_INTEGRITY_STREAM
+ !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_INTEGRITY_STREAM) &&
+#endif
+#ifdef FILE_ATTRIBUTE_NO_SCRUB_DATA
+ !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NO_SCRUB_DATA) &&
+#endif
+ !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_OFFLINE) &&
+ !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY));
+#else
+ S_ISREG(file->_s.st_mode);
+#endif
+
+ return 0;
+}
+
+_TINYDIR_FUNC
+int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i)
+{
+ if (dir == NULL || file == NULL)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+ if (i >= dir->n_files)
+ {
+ errno = ENOENT;
+ return -1;
+ }
+
+ memcpy(file, &dir->_files[i], sizeof(tinydir_file));
+ _tinydir_get_ext(file);
+
+ return 0;
+}
+
+_TINYDIR_FUNC
+int tinydir_open_subdir_n(tinydir_dir *dir, size_t i)
+{
+ _tinydir_char_t path[_TINYDIR_PATH_MAX];
+ if (dir == NULL)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+ if (i >= dir->n_files || !dir->_files[i].is_dir)
+ {
+ errno = ENOENT;
+ return -1;
+ }
+
+ _tinydir_strcpy(path, dir->_files[i].path);
+ tinydir_close(dir);
+ if (tinydir_open_sorted(dir, path) == -1)
+ {
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Open a single file given its path */
+_TINYDIR_FUNC
+int tinydir_file_open(tinydir_file *file, const _tinydir_char_t *path)
+{
+ tinydir_dir dir;
+ int result = 0;
+ int found = 0;
+ _tinydir_char_t dir_name_buf[_TINYDIR_PATH_MAX];
+ _tinydir_char_t file_name_buf[_TINYDIR_FILENAME_MAX];
+ _tinydir_char_t *dir_name;
+ _tinydir_char_t *base_name;
+#if (defined _MSC_VER || defined __MINGW32__)
+ _tinydir_char_t drive_buf[_TINYDIR_PATH_MAX];
+ _tinydir_char_t ext_buf[_TINYDIR_FILENAME_MAX];
+#endif
+
+ if (file == NULL || path == NULL || _tinydir_strlen(path) == 0)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+ if (_tinydir_strlen(path) + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX)
+ {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ /* Get the parent path */
+#if (defined _MSC_VER || defined __MINGW32__)
+#if ((defined _MSC_VER) && (_MSC_VER >= 1400))
+ errno = _tsplitpath_s(
+ path,
+ drive_buf, _TINYDIR_DRIVE_MAX,
+ dir_name_buf, _TINYDIR_FILENAME_MAX,
+ file_name_buf, _TINYDIR_FILENAME_MAX,
+ ext_buf, _TINYDIR_FILENAME_MAX);
+#else
+ _tsplitpath(
+ path,
+ drive_buf,
+ dir_name_buf,
+ file_name_buf,
+ ext_buf);
+#endif
+
+ if (errno)
+ {
+ return -1;
+ }
+
+/* _splitpath_s not work fine with only filename and widechar support */
+#ifdef _UNICODE
+ if (drive_buf[0] == L'\xFEFE')
+ drive_buf[0] = '\0';
+ if (dir_name_buf[0] == L'\xFEFE')
+ dir_name_buf[0] = '\0';
+#endif
+
+ /* Emulate the behavior of dirname by returning "." for dir name if it's
+ empty */
+ if (drive_buf[0] == '\0' && dir_name_buf[0] == '\0')
+ {
+ _tinydir_strcpy(dir_name_buf, TINYDIR_STRING("."));
+ }
+ /* Concatenate the drive letter and dir name to form full dir name */
+ _tinydir_strcat(drive_buf, dir_name_buf);
+ dir_name = drive_buf;
+ /* Concatenate the file name and extension to form base name */
+ _tinydir_strcat(file_name_buf, ext_buf);
+ base_name = file_name_buf;
+#else
+ _tinydir_strcpy(dir_name_buf, path);
+ dir_name = dirname(dir_name_buf);
+ _tinydir_strcpy(file_name_buf, path);
+ base_name = basename(file_name_buf);
+#endif
+
+ /* Special case: if the path is a root dir, open the parent dir as the file */
+#if (defined _MSC_VER || defined __MINGW32__)
+ if (_tinydir_strlen(base_name) == 0)
+#else
+ if ((_tinydir_strcmp(base_name, TINYDIR_STRING("/"))) == 0)
+#endif
+ {
+ memset(file, 0, sizeof * file);
+ file->is_dir = 1;
+ file->is_reg = 0;
+ _tinydir_strcpy(file->path, dir_name);
+ file->extension = file->path + _tinydir_strlen(file->path);
+ return 0;
+ }
+
+ /* Open the parent directory */
+ if (tinydir_open(&dir, dir_name) == -1)
+ {
+ return -1;
+ }
+
+ /* Read through the parent directory and look for the file */
+ while (dir.has_next)
+ {
+ if (tinydir_readfile(&dir, file) == -1)
+ {
+ result = -1;
+ goto bail;
+ }
+ if (_tinydir_strcmp(file->name, base_name) == 0)
+ {
+ /* File found */
+ found = 1;
+ break;
+ }
+ tinydir_next(&dir);
+ }
+ if (!found)
+ {
+ result = -1;
+ errno = ENOENT;
+ }
+
+bail:
+ tinydir_close(&dir);
+ return result;
+}
+
+_TINYDIR_FUNC
+void _tinydir_get_ext(tinydir_file *file)
+{
+ _tinydir_char_t *period = _tinydir_strrchr(file->name, TINYDIR_STRING('.'));
+ if (period == NULL)
+ {
+ file->extension = &(file->name[_tinydir_strlen(file->name)]);
+ }
+ else
+ {
+ file->extension = period + 1;
+ }
+}
+
+_TINYDIR_FUNC
+int _tinydir_file_cmp(const void *a, const void *b)
+{
+ const tinydir_file *fa = (const tinydir_file *)a;
+ const tinydir_file *fb = (const tinydir_file *)b;
+ if (fa->is_dir != fb->is_dir)
+ {
+ return -(fa->is_dir - fb->is_dir);
+ }
+ return _tinydir_strncmp(fa->name, fb->name, _TINYDIR_FILENAME_MAX);
+}
+
+#ifndef _MSC_VER
+#ifndef _TINYDIR_USE_READDIR
+/*
+The following authored by Ben Hutchings
+from https://womble.decadent.org.uk/readdir_r-advisory.html
+*/
+/* Calculate the required buffer size (in bytes) for directory *
+* entries read from the given directory handle. Return -1 if this *
+* this cannot be done. *
+* *
+* This code does not trust values of NAME_MAX that are less than *
+* 255, since some systems (including at least HP-UX) incorrectly *
+* define it to be a smaller value. */
+_TINYDIR_FUNC
+size_t _tinydir_dirent_buf_size(_TINYDIR_DIR *dirp)
+{
+ long name_max;
+ size_t name_end;
+ /* parameter may be unused */
+ (void)dirp;
+
+#if defined _TINYDIR_USE_FPATHCONF
+ name_max = fpathconf(dirfd(dirp), _PC_NAME_MAX);
+ if (name_max == -1)
+#if defined(NAME_MAX)
+ name_max = (NAME_MAX > 255) ? NAME_MAX : 255;
+#else
+ return (size_t)(-1);
+#endif
+#elif defined(NAME_MAX)
+ name_max = (NAME_MAX > 255) ? NAME_MAX : 255;
+#else
+#error "buffer size for readdir_r cannot be determined"
+#endif
+ name_end = (size_t)offsetof(struct _tinydir_dirent, d_name) + name_max + 1;
+ return (name_end > sizeof(struct _tinydir_dirent) ?
+ name_end : sizeof(struct _tinydir_dirent));
+}
+#endif
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+# if defined (_MSC_VER)
+# pragma warning(pop)
+# endif
+
+#endif
diff --git a/include/disp.h b/include/disp.h
new file mode 100644
index 0000000..3e1d7b9
--- /dev/null
+++ b/include/disp.h
@@ -0,0 +1,46 @@
+#ifndef TREEE_DISP_H_INCLUDED
+#define TREEE_DISP_H_INCLUDED
+/* /////////////////////////////////////////////////////////////////////////////
+// _
+// | |_ _ __ ___ ___ ___ treee: interactive file tree viewer [0.1.0]
+// | __| '__/ _ \/ _ \/ _ \ Copyright (C) 2020 Justin Collier
+// | |_| | | __/ __/ __/
+// \__|_| \___|\___|\___| - - - - - - - - - - - - - - - - - -
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the internalied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+// /
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . ///
+///////////////////////////////////////////////////////////////////////////// */
+
+#include "file.h"
+
+#define PAIR_CYAN 1
+#define PAIR_MAGENTA 2
+#define PAIR_GREEN 3
+#define PAIR_ERROR 4
+
+typedef struct window {
+ long x;
+ long y;
+ int showhidden;
+} view;
+
+void
+printtree(file *node, long *ln, view *win, long *max_x);
+
+void
+initcurses();
+
+void
+destroycurses();
+
+#endif
diff --git a/include/file.h b/include/file.h
new file mode 100644
index 0000000..9767cb1
--- /dev/null
+++ b/include/file.h
@@ -0,0 +1,69 @@
+#ifndef TREEE_FILE_H_INCLUDED
+#define TREEE_FILE_H_INCLUDED
+/* /////////////////////////////////////////////////////////////////////////////
+// _
+// | |_ _ __ ___ ___ ___ treee: interactive file tree viewer [0.1.0]
+// | __| '__/ _ \/ _ \/ _ \ Copyright (C) 2020 Justin Collier
+// | |_| | | __/ __/ __/
+// \__|_| \___|\___|\___| - - - - - - - - - - - - - - - - - -
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the internalied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+// /
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . ///
+///////////////////////////////////////////////////////////////////////////// */
+
+#include
+
+#include "str.h"
+#include "tinydir.h"
+
+/*.----------------------------------------------------------------------------,
+ / file */
+
+typedef struct file file;
+struct file {
+ str path;
+ str name;
+ str symlink_target;
+ long level;
+ int is_dir;
+ int is_lnk;
+ int is_valid_lnk;
+ file *parent;
+ file *first_child;
+ file *next_sibling;
+};
+
+file
+file_ctor(
+ file *parent, const char *filename, const int is_dir, const int is_lnk);
+
+file *
+file_new(
+ file *parent, const char *filename, const int is_dir, const int is_lnk);
+
+void
+file_insert(file *parent, file *newchild);
+
+void
+file_destruct(file *f);
+
+void
+file_attach(file *parent, tinydir_dir *dir);
+
+int
+file_has_siblings(file *node);
+
+int
+file_is_last_sibling(file *node);
+
+#endif
diff --git a/src/disp.c b/src/disp.c
new file mode 100644
index 0000000..0b43201
--- /dev/null
+++ b/src/disp.c
@@ -0,0 +1,129 @@
+/* /////////////////////////////////////////////////////////////////////////////
+// _
+// | |_ _ __ ___ ___ ___ treee: interactive file tree viewer [0.1.0]
+// | __| '__/ _ \/ _ \/ _ \ Copyright (C) 2020 Justin Collier
+// | |_| | | __/ __/ __/
+// \__|_| \___|\___|\___| - - - - - - - - - - - - - - - - - -
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the internalied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+// /
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . ///
+///////////////////////////////////////////////////////////////////////////// */
+
+#include
+
+#include "disp.h"
+
+static void
+mvaddstr_bounded(long y, long x, const char *text, size_t len, long *max_x) {
+ if (x >= 0) {
+ mvaddstr(y, x, text);
+ } else if ((-x + 2) < len) {
+ mvaddch(y, 0, '<');
+ mvaddstr(y, 2, &text[-x + 2]);
+ }
+}
+
+void
+printtree(file *node, long *ln, view *vw, long *max_x) {
+ long basex = node->level * 2 - vw->x;
+ long basey = *ln - vw->y;
+ if (node->name[0] == '.' && node->name[1] != '\0' && !vw->showhidden)
+ return;
+ if (basey >= 0 && basey <= LINES && basex <= COLS) {
+ attron(COLOR_PAIR(PAIR_MAGENTA));
+ if (file_has_siblings(node) && strcmp(node->path, ".") != 0) {
+ if (file_is_last_sibling(node)) {
+ mvaddch(basey, basex - 2, ACS_LLCORNER); // └
+ } else {
+ mvaddch(basey, basex - 2, ACS_LTEE); // ├
+ }
+ } else if (node->parent != NULL)
+ // only child
+ {
+ mvaddch(basey, basex - 2, ACS_LLCORNER); // └
+ }
+
+ {
+ int x = basex - 2;
+ for (file *f = node->parent; f != NULL; f = f->parent) {
+ x -= 2;
+ if (!file_is_last_sibling(f)) {
+ mvaddch(basey, x, ACS_VLINE);
+ }
+ }
+ }
+
+ attroff(COLOR_PAIR(PAIR_MAGENTA));
+
+ if (node->is_dir && !node->is_lnk) {
+ attron(COLOR_PAIR(PAIR_GREEN));
+ mvaddstr_bounded(basey, basex, node->name, str_len(node->name), max_x);
+ if (node->level * 2 + str_len(node->name) > *max_x)
+ *max_x = node->level * 2 + str_len(node->name);
+ attroff(COLOR_PAIR(PAIR_GREEN));
+ } else {
+ if (node->is_lnk) {
+ attron(COLOR_PAIR(PAIR_CYAN));
+ mvaddstr_bounded(basey, basex, node->name, str_len(node->name), max_x);
+ attroff(COLOR_PAIR(PAIR_CYAN));
+ mvaddstr_bounded(
+ basey, basex + str_len(node->name) + 1, "->", 2, max_x);
+ if (!node->is_valid_lnk) {
+ attron(COLOR_PAIR(PAIR_ERROR));
+ attron(A_BOLD);
+ }
+ mvaddstr_bounded(
+ basey, basex + str_len(node->name) + 4, node->symlink_target,
+ str_len(node->symlink_target), max_x);
+ if (!node->is_valid_lnk) {
+ attroff(COLOR_PAIR(PAIR_ERROR));
+ attroff(A_BOLD);
+ }
+ if (node->level * 2 + str_len(node->name) + 4 +
+ str_len(node->symlink_target) >
+ *max_x)
+ *max_x = node->level * 2 + str_len(node->name) + 4 +
+ str_len(node->symlink_target);
+ } else {
+ mvaddstr_bounded(basey, basex, node->name, str_len(node->name), max_x);
+ if (node->level * 2 + str_len(node->name) > *max_x)
+ *max_x = node->level * 2 + str_len(node->name);
+ }
+ }
+ }
+
+ ++(*ln);
+ for (file *f = node->first_child; f != NULL; f = f->next_sibling) {
+ printtree(f, ln, vw, max_x);
+ }
+}
+
+void
+initcurses() {
+ initscr();
+ cbreak();
+ noecho();
+ clear();
+ start_color();
+ use_default_colors();
+ init_pair(PAIR_CYAN, COLOR_CYAN, -1);
+ init_pair(PAIR_MAGENTA, COLOR_MAGENTA, -1);
+ init_pair(PAIR_GREEN, COLOR_GREEN, -1);
+ init_pair(PAIR_ERROR, COLOR_WHITE, COLOR_RED);
+}
+
+void
+destroycurses() {
+ clear();
+ endwin();
+}
diff --git a/src/file.c b/src/file.c
new file mode 100644
index 0000000..4e05827
--- /dev/null
+++ b/src/file.c
@@ -0,0 +1,185 @@
+/* /////////////////////////////////////////////////////////////////////////////
+// _
+// | |_ _ __ ___ ___ ___ treee: interactive file tree viewer [0.1.0]
+// | __| '__/ _ \/ _ \/ _ \ Copyright (C) 2020 Justin Collier
+// | |_| | | __/ __/ __/
+// \__|_| \___|\___|\___| - - - - - - - - - - - - - - - - - -
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the internalied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+// /
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . ///
+///////////////////////////////////////////////////////////////////////////// */
+
+#include "file.h"
+
+#include
+
+/*.----------------------------------------------------------------------------,
+ / helpers */
+
+static int
+alphacmp(const char *a, const char *b) {
+ int i = 0;
+ for (; a[i] != '\0' && b[i] != '\0'; ++i) {
+ /* if (a[i] > b[i]) */
+ /* return 1; */
+ /* else if (a[i] < b[i]) */
+ /* return -1; */
+ if (a[i] != b[i])
+ return -1 * (a[i] < b[i]) + (a[i] > b[i]);
+ }
+ /* if (a[i] == b[i]) return 0; */
+ /* else if (a[i] == '\0') return -1; */
+ /* else return 1; */
+ return (a[i] != b[i]) * (-2 * (a[i] == '\0')) + 1;
+}
+
+/*.----------------------------------------------------------------------------,
+ / file */
+
+file
+file_ctor(
+ file *parent, const char *filename, const int is_dir, const int is_lnk) {
+ str path;
+ long level;
+ if (parent == NULL) {
+ path = str_new(filename);
+ level = 0;
+ } else {
+ path = str_alloc(str_len(parent->path) + strlen(filename) + 1);
+ str_append_(&path, parent->path);
+ str_append(&path, "/");
+ str_append(&path, filename);
+ level = parent->level + 1;
+ }
+
+ char buf[_TINYDIR_PATH_MAX];
+ size_t len = readlink(path, buf, sizeof(buf) - 1);
+ if (len != -1) {
+ buf[len] = '\0';
+ }
+ int is_valid_lnk = 0;
+ if (len != -1 && access(buf, F_OK) != -1) {
+ is_valid_lnk = 1;
+ }
+
+ return (file){
+ .path = path,
+ .name = str_new(filename),
+ .symlink_target = len != -1 ? str_new(buf) : str_new(""),
+ .level = level,
+ .is_dir = is_dir,
+ .is_lnk = len != -1,
+ .is_valid_lnk = is_valid_lnk,
+ .parent = parent,
+ .first_child = NULL,
+ .next_sibling = NULL,
+ };
+}
+
+file *
+file_new(
+ file *parent, const char *filename, const int is_dir, const int is_lnk) {
+ file *o = malloc(sizeof(file));
+ file obj = file_ctor(parent, filename, is_dir, is_lnk);
+ memcpy(o, &obj, sizeof(obj));
+ return o;
+}
+
+void
+file_insert(file *parent, file *newchild) {
+ file *cur = parent->first_child;
+ newchild->parent = parent;
+
+ if (cur == NULL) {
+ parent->first_child = newchild;
+ return;
+ }
+
+ // if child name < first_child name, make it the first child and return
+ if (alphacmp(newchild->name, cur->name) < 0) {
+ newchild->next_sibling = cur;
+ parent->first_child = newchild;
+ return;
+ }
+
+ // loop through all siblings until child is < the next sibling (or next
+ // sibling is NULL), insert between cur and next and return
+ for (; cur->next_sibling != NULL; cur = cur->next_sibling) {
+ if (alphacmp(newchild->name, cur->next_sibling->name) < 0) {
+ newchild->next_sibling = cur->next_sibling;
+ cur->next_sibling = newchild;
+ return;
+ }
+ }
+
+ // next_sibling is NULL; set it to child
+ cur->next_sibling = newchild;
+}
+
+// destroys all children and their siblings recursively
+void
+file_destruct(file *f) {
+ for (file *cur = f->first_child; cur != NULL; cur = cur->next_sibling) {
+ file_destruct(cur);
+ }
+ str_free(&f->path);
+ str_free(&f->name);
+ str_free(&f->symlink_target);
+ free(f);
+}
+
+void
+file_attach(file *parent, tinydir_dir *dir) {
+ while (dir->has_next) {
+ tinydir_file tdf;
+ tinydir_readfile(dir, &tdf);
+
+ if (strcmp(tdf.name, ".") == 0 || strcmp(tdf.name, "..") == 0) {
+ tinydir_next(dir);
+ continue; // skip ./ and ../
+ }
+
+ file *next =
+ file_new(parent, tdf.name, tdf.is_dir, S_ISREG(tdf._s.st_mode));
+ file_insert(parent, next);
+
+ if (tdf.is_dir) {
+ tinydir_dir tdnext;
+ tinydir_open(&tdnext, next->path);
+ file_attach(next, &tdnext);
+ tinydir_close(&tdnext);
+ }
+
+ tinydir_next(dir);
+ }
+}
+
+int
+file_has_siblings(file *node) {
+ file *parent = node->parent;
+ if (parent == NULL)
+ return 1;
+ return node != parent->first_child || node->next_sibling != NULL;
+}
+
+int
+file_is_last_sibling(file *node) {
+ file *parent = node->parent;
+ if (parent == NULL)
+ return 1;
+ file *lst;
+ for (lst = parent->first_child; lst->next_sibling != NULL;
+ lst = lst->next_sibling)
+ ;
+ return lst == node;
+}
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..60be85a
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,132 @@
+/* /////////////////////////////////////////////////////////////////////////////
+// _
+// | |_ _ __ ___ ___ ___ treee: interactive file tree viewer [0.1.0]
+// | __| '__/ _ \/ _ \/ _ \ Copyright (C) 2020 Justin Collier
+// | |_| | | __/ __/ __/
+// \__|_| \___|\___|\___| - - - - - - - - - - - - - - - - - -
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the internalied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+// /
+// You should have received a copy of the GNU General Public License //
+// along with this program. If not, see . ///
+///////////////////////////////////////////////////////////////////////////// */
+
+#include
+
+#include "disp.h"
+#include "file.h"
+
+int
+main(int argc, char *argv[]) {
+ initcurses();
+
+ const char *searchdir = (argc < 2 ? "." : argv[1]);
+
+ view vw = {
+ .x = 0,
+ .y = 0,
+ .showhidden = 0,
+ };
+
+ while (1) {
+ clear();
+ tinydir_dir dir;
+ tinydir_open(&dir, searchdir);
+
+ file *root = file_new(NULL, searchdir, 1, 0);
+
+ file_attach(root, &dir);
+ long lines = 0;
+ long max_x = 0;
+ printtree(root, &lines, &vw, &max_x);
+ if (max_x < vw.x + 3) {
+ if (max_x >= 3)
+ vw.x = max_x - 3;
+ else
+ vw.x = 0;
+ continue;
+ }
+
+ cbreak();
+
+ timeout(1000);
+ int c = getch();
+
+ switch (c) {
+ case 'j': {
+ if (lines < LINES)
+ break;
+ if (vw.y + 1 < lines - LINES + 1)
+ vw.y += 1;
+ break;
+ }
+ case 'J': {
+ if (lines < LINES)
+ break;
+ if (vw.y + 10 < lines - LINES + 1)
+ vw.y += 10;
+ else if (vw.y + 10 >= lines - LINES + 1)
+ vw.y = lines - LINES;
+ break;
+ }
+ case 'k': {
+ if (vw.y - 1 >= 0)
+ vw.y -= 1;
+ break;
+ }
+ case 'K': {
+ if (vw.y - 10 >= 0)
+ vw.y -= 10;
+ else if (vw.y < 10)
+ vw.y = 0;
+ break;
+ }
+ case 'l': {
+ if (vw.x + 3 < max_x)
+ vw.x += 1;
+ break;
+ }
+ case 'L': {
+ if (vw.x + 12 < max_x)
+ vw.x += 10;
+ else if (vw.x + 12 >= max_x)
+ vw.x = max_x - 2;
+ break;
+ }
+ case 'h': {
+ if (vw.x - 1 >= 0)
+ vw.x -= 1;
+ break;
+ }
+ case 'H': {
+ if (vw.x - 10 >= 0)
+ vw.x -= 10;
+ else if (vw.x < 10)
+ vw.x = 0;
+ break;
+ }
+ case 'q': {
+ destroycurses();
+ file_destruct(root);
+ tinydir_close(&dir);
+ return EXIT_SUCCESS;
+ break;
+ }
+ case '.': {
+ vw.showhidden = !vw.showhidden;
+ break;
+ }
+ }
+
+ file_destruct(root);
+ tinydir_close(&dir);
+ }
+}