aboutsummaryrefslogtreecommitdiffstats
path: root/include/project.h
blob: 4cf3bf686fb8ac9b81327d8db18e12f1ba5cdf0a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/* Projects on disk, and the workspaces they open as.
 *
 * A *project* is a directory somebody works in. A *workspace* is the tab it
 * currently occupies in a session. This file is about the first: finding them,
 * naming them, and saying where each one's layout lives. The second lives in
 * app.c, because a workspace is tabs.
 *
 * The list is derived when it is asked for rather than watched. One readdir per
 * root plus two faccessat per entry is forty checkouts for eighty-one syscalls,
 * which is nothing next to the keystroke that asked -- and an answer nobody
 * remembered cannot be stale, which no watcher can promise on a bind mount.
 */
#ifndef SLOSH_PROJECT_H
#define SLOSH_PROJECT_H

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

/* The file a project uses to say what it needs. One name, so there is no
 * precedence question. `.layout` rather than `.kdl`: zellij speaks the same
 * syntax under the same extension with a different schema, and an extension
 * that names the wrong owner is worse than one that does not name the
 * parser -- the file says what it is, `--check` says how it was read. */
#define PROJECT_LAYOUT_FILE "slosh.layout"
/* The spelling this replaced. Still *found* by the project scan, so nobody's
 * checked-in workspace vanishes on upgrade; everything slosh writes is the
 * new name, and the new name wins where both exist. */
#define PROJECT_LAYOUT_FILE_OLD "slosh.layout.kdl"

/* How many projects one session will look at. A picker is not a filesystem
 * browser: past this many the answer to "which project" is a search, not a
 * list, and the list is what this is for. */
#define PROJECTS_MAX 128

typedef struct {
  char name[64];  /* the directory's own name, which is what people call it */
  char path[512]; /* absolute, resolved */
  char slug[64];  /* `project:name.hash8` -- the purpose a workspace carries */
  /* The project's own layout file, or "" for one inferred from a `.git`. The
   * distinction is the whole of what "declared" means here, and the picker
   * shows it: a project with no layout is an invitation to save one. */
  char layout[512];
  int64_t mtime; /* of `layout`, 0 when there is none. Drift is derivable from
                  * this and a pane's `since` without anything storing it. */
} project_t;

/* Every project under `roots`, sorted by name. `roots` is a NUL-terminated
 * array of directories; `depth` is how many levels below each to look, and a
 * directory that *is* a project is never descended into -- which is what keeps
 * this off node_modules without a rule about node_modules.
 *
 * A root that does not exist is not an error. Neither is one with nothing in
 * it: the setting is a statement of where you keep things, not a promise that
 * you have any. */
size_t project_scan(const char *const *roots, int depth, project_t *out,
                    size_t max);

/* One project by name, or by path when `name` looks like one (it has a `/`).
 * False when no root holds it -- which is also the containment check, since a
 * path that no root holds is a path this session will not write to. */
bool project_find(const char *const *roots, int depth, const char *name,
                  project_t *out);

/* The purpose a workspace for this directory carries: `project:<name>.<hash>`
 * of the resolved path. Hashed on the path rather than the name so that two
 * worktrees of one repo are two workspaces, which is what they are. */
void project_slug(const char *path, const char *name, char *out, size_t cap);

#endif /* SLOSH_PROJECT_H */