summaryrefslogtreecommitdiff
path: root/examples/dte/tag.c
diff options
context:
space:
mode:
Diffstat (limited to 'examples/dte/tag.c')
-rw-r--r--examples/dte/tag.c322
1 files changed, 322 insertions, 0 deletions
diff --git a/examples/dte/tag.c b/examples/dte/tag.c
new file mode 100644
index 0000000..bd030c0
--- /dev/null
+++ b/examples/dte/tag.c
@@ -0,0 +1,322 @@
+#include <errno.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include "tag.h"
+#include "error.h"
+#include "util/debug.h"
+#include "util/log.h"
+#include "util/path.h"
+#include "util/str-util.h"
+#include "util/xmalloc.h"
+#include "util/xreadwrite.h"
+
+static const char *current_filename; // For sorting tags
+
+static int visibility_cmp(const Tag *a, const Tag *b)
+{
+ bool a_this_file = false;
+ bool b_this_file = false;
+
+ if (!a->local && !b->local) {
+ return 0;
+ }
+
+ // Is tag visibility limited to the current file?
+ if (a->local) {
+ a_this_file = current_filename && strview_equal_cstring(&a->filename, current_filename);
+ }
+ if (b->local) {
+ b_this_file = current_filename && strview_equal_cstring(&b->filename, current_filename);
+ }
+
+ // Tags local to other file than current are not interesting
+ if (a->local && !a_this_file) {
+ // a is not interesting
+ if (b->local && !b_this_file) {
+ // b is equally uninteresting
+ return 0;
+ }
+ // b is more interesting, sort it before a
+ return 1;
+ }
+ if (b->local && !b_this_file) {
+ // b is not interesting
+ return -1;
+ }
+
+ // Both are NOT UNinteresting
+
+ if (a->local && a_this_file) {
+ if (b->local && b_this_file) {
+ return 0;
+ }
+ // a is more interesting because it is local symbol
+ return -1;
+ }
+ if (b->local && b_this_file) {
+ // b is more interesting because it is local symbol
+ return 1;
+ }
+ return 0;
+}
+
+static int kind_cmp(const Tag *a, const Tag *b)
+{
+ if (a->kind == b->kind) {
+ return 0;
+ }
+
+ // Struct member (m) is not very interesting
+ if (a->kind == 'm') {
+ return 1;
+ }
+ if (b->kind == 'm') {
+ return -1;
+ }
+
+ // Global variable (v) is not very interesting
+ if (a->kind == 'v') {
+ return 1;
+ }
+ if (b->kind == 'v') {
+ return -1;
+ }
+
+ // Struct (s), union (u)
+ return 0;
+}
+
+static int tag_cmp(const void *ap, const void *bp)
+{
+ const Tag *const *a = ap;
+ const Tag *const *b = bp;
+ int r = visibility_cmp(*a, *b);
+ return r ? r : kind_cmp(*a, *b);
+}
+
+// Find "tags" file from directory path and its parent directories
+static int open_tag_file(char *path)
+{
+ static const char tags[] = "tags";
+ while (*path) {
+ size_t len = strlen(path);
+ char *slash = strrchr(path, '/');
+ if (slash != path + len - 1) {
+ path[len++] = '/';
+ }
+ memcpy(path + len, tags, sizeof(tags));
+ int fd = xopen(path, O_RDONLY | O_CLOEXEC, 0);
+ if (fd >= 0) {
+ return fd;
+ }
+ if (errno != ENOENT) {
+ return -1;
+ }
+ *slash = '\0';
+ }
+ errno = ENOENT;
+ return -1;
+}
+
+static bool tag_file_changed (
+ const TagFile *tf,
+ const char *filename,
+ const struct stat *st
+) {
+ return tf->mtime != st->st_mtime || !streq(tf->filename, filename);
+}
+
+// Note: does not free `tf` itself
+void tag_file_free(TagFile *tf)
+{
+ free(tf->filename);
+ free(tf->buf);
+ *tf = (TagFile){.filename = NULL};
+}
+
+static bool load_tag_file(TagFile *tf)
+{
+ char path[4096];
+ if (unlikely(!getcwd(path, sizeof(path) - STRLEN("/tags")))) {
+ LOG_ERRNO("getcwd");
+ return false;
+ }
+
+ int fd = open_tag_file(path);
+ if (fd < 0) {
+ return false;
+ }
+
+ struct stat st;
+ if (unlikely(fstat(fd, &st) != 0)) {
+ LOG_ERRNO("fstat");
+ xclose(fd);
+ return false;
+ }
+
+ if (unlikely(st.st_size <= 0)) {
+ xclose(fd);
+ return false;
+ }
+
+ if (tf->filename) {
+ if (!tag_file_changed(tf, path, &st)) {
+ xclose(fd);
+ return true;
+ }
+ tag_file_free(tf);
+ BUG_ON(tf->filename);
+ }
+
+ char *buf = malloc(st.st_size);
+ if (unlikely(!buf)) {
+ LOG_ERRNO("malloc");
+ xclose(fd);
+ return false;
+ }
+
+ ssize_t size = xread_all(fd, buf, st.st_size);
+ xclose(fd);
+ if (size < 0) {
+ free(buf);
+ return false;
+ }
+
+ *tf = (TagFile) {
+ .filename = xstrdup(path),
+ .buf = buf,
+ .size = size,
+ .mtime = st.st_mtime,
+ };
+
+ return true;
+}
+
+static void free_tags_cb(Tag *t)
+{
+ free_tag(t);
+ free(t);
+}
+
+static void free_tags(PointerArray *tags)
+{
+ ptr_array_free_cb(tags, FREE_FUNC(free_tags_cb));
+}
+
+// Both parameters must be absolute and clean
+static const char *path_slice_relative(const char *filename, const StringView dir)
+{
+ if (strncmp(filename, dir.data, dir.length) != 0) {
+ // Filename doesn't start with dir
+ return NULL;
+ }
+ switch (filename[dir.length]) {
+ case '\0': // Equal strings
+ return ".";
+ case '/':
+ return filename + dir.length + 1;
+ }
+ return NULL;
+}
+
+static void tag_file_find_tags (
+ const TagFile *tf,
+ const char *filename,
+ const StringView *name,
+ PointerArray *tags
+) {
+ Tag *t = xnew(Tag, 1);
+ size_t pos = 0;
+ while (next_tag(tf->buf, tf->size, &pos, name, true, t)) {
+ ptr_array_append(tags, t);
+ t = xnew(Tag, 1);
+ }
+ free(t);
+
+ if (!filename) {
+ current_filename = NULL;
+ } else {
+ StringView dir = path_slice_dirname(tf->filename);
+ current_filename = path_slice_relative(filename, dir);
+ }
+ ptr_array_sort(tags, tag_cmp);
+ current_filename = NULL;
+}
+
+// Note: this moves ownership of tag->pattern to the generated Message
+// and assigns NULL to the old pointer
+void add_message_for_tag(MessageArray *messages, Tag *tag, const StringView *dir)
+{
+ BUG_ON(dir->length == 0);
+ BUG_ON(dir->data[0] != '/');
+
+ static const char prefix[] = "Tag ";
+ size_t prefix_len = sizeof(prefix) - 1;
+ size_t msg_len = prefix_len + tag->name.length;
+ Message *m = xmalloc(sizeof(*m) + msg_len + 1);
+
+ memcpy(m->msg, prefix, prefix_len);
+ memcpy(m->msg + prefix_len, tag->name.data, tag->name.length);
+ m->msg[msg_len] = '\0';
+
+ m->loc = xnew0(FileLocation, 1);
+ m->loc->filename = path_join_sv(dir, &tag->filename, false);
+
+ if (tag->pattern) {
+ m->loc->pattern = tag->pattern; // Message takes ownership
+ tag->pattern = NULL;
+ } else {
+ m->loc->line = tag->lineno;
+ }
+
+ add_message(messages, m);
+}
+
+size_t tag_lookup(TagFile *tf, const StringView *name, const char *filename, MessageArray *messages)
+{
+ clear_messages(messages);
+ if (!load_tag_file(tf)) {
+ error_msg("No tags file");
+ return 0;
+ }
+
+ // Filename helps to find correct tags
+ PointerArray tags = PTR_ARRAY_INIT;
+ tag_file_find_tags(tf, filename, name, &tags);
+
+ size_t ntags = tags.count;
+ if (ntags == 0) {
+ error_msg("Tag '%.*s' not found", (int)name->length, name->data);
+ return 0;
+ }
+
+ StringView tf_dir = path_slice_dirname(tf->filename);
+ for (size_t i = 0; i < ntags; i++) {
+ Tag *tag = tags.ptrs[i];
+ add_message_for_tag(messages, tag, &tf_dir);
+ }
+
+ free_tags(&tags);
+ return ntags;
+}
+
+void collect_tags(TagFile *tf, PointerArray *a, const StringView *prefix)
+{
+ if (!load_tag_file(tf)) {
+ return;
+ }
+
+ Tag t;
+ size_t pos = 0;
+ StringView prev = STRING_VIEW_INIT;
+ while (next_tag(tf->buf, tf->size, &pos, prefix, false, &t)) {
+ BUG_ON(t.name.length == 0);
+ if (prev.length == 0 || !strview_equal(&t.name, &prev)) {
+ ptr_array_append(a, xstrcut(t.name.data, t.name.length));
+ prev = t.name;
+ }
+ free_tag(&t);
+ }
+}