mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-04-28 06:46:38 -04:00
commit
52af2694ff
9 changed files with 253 additions and 64 deletions
|
|
@ -50,6 +50,8 @@
|
||||||
### Deprecated
|
### Deprecated
|
||||||
|
|
||||||
* **scrollback** option in `footrc`. Use **scrollback.lines** instead.
|
* **scrollback** option in `footrc`. Use **scrollback.lines** instead.
|
||||||
|
* `$XDG_CONFIG_HOME/footrc`/`~/.config/footrc`. Use
|
||||||
|
`$XDG_CONFIG_HOME/foot/foot.ini`/`~/.config/foot/foot.ini` instead.
|
||||||
|
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
@ -64,6 +66,13 @@
|
||||||
* Trailing empty cells are no longer highlighted in mouse selections.
|
* Trailing empty cells are no longer highlighted in mouse selections.
|
||||||
* Scrollback position indicator is now based on the number of _used_
|
* Scrollback position indicator is now based on the number of _used_
|
||||||
scrollback lines, instead of the _total_ number of scrollback lines.
|
scrollback lines, instead of the _total_ number of scrollback lines.
|
||||||
|
* Foot now searches for its configuration in
|
||||||
|
`$XDG_CONFIG_HOME/foot/foot.ini` (typically
|
||||||
|
`~/.config/foot/foot.ini`), before looking for
|
||||||
|
`$XDG_CONFIG_HOME/footrc`.
|
||||||
|
* Foot now searches for its configuration in
|
||||||
|
`$XDG_DATA_DIRS/foot/foot.ini`, if no configuration is found in
|
||||||
|
`$XDG_CONFIG_HOME/foot/foot.ini` or in `$XDG_CONFIG_HOME/footrc`.
|
||||||
|
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
|
||||||
13
README.md
13
README.md
|
|
@ -54,11 +54,12 @@ See [INSTALL.md](INSTALL.md).
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
**foot** can be configured by creating a file
|
**foot** can be configured by creating a file
|
||||||
`$XDG_CONFIG_HOME/footrc` (defaulting to `~/.config/footrc`). A
|
`$XDG_CONFIG_HOME/foot/foot.ini` (defaulting to
|
||||||
template for that can usually be found in `/usr/share/foot/footrc` or
|
`~/.config/foot/foot.ini`). A template for that can usually be found
|
||||||
[here](https://codeberg.org/dnkl/foot/src/branch/master/footrc).
|
in `/usr/share/foot/foot.ini` or
|
||||||
|
[here](https://codeberg.org/dnkl/foot/src/branch/master/foot.ini).
|
||||||
|
|
||||||
Further information can be found in foot's man page `footrc(5)`.
|
Further information can be found in foot's man page `foot.ini(5)`.
|
||||||
|
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
@ -104,8 +105,8 @@ fonts, _then_ fontconfig's list is used.
|
||||||
|
|
||||||
## Shortcuts
|
## Shortcuts
|
||||||
|
|
||||||
These are the default shortcuts. See `man 5 foot` and the example
|
These are the default shortcuts. See `man foot.ini` and the example
|
||||||
`footrc` to see how these can be changed.
|
`foot.ini` to see how these can be changed.
|
||||||
|
|
||||||
|
|
||||||
### Keyboard
|
### Keyboard
|
||||||
|
|
|
||||||
260
config.c
260
config.c
|
|
@ -6,11 +6,13 @@
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <pwd.h>
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <pwd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
#include <linux/input-event-codes.h>
|
#include <linux/input-event-codes.h>
|
||||||
#include <xkbcommon/xkbcommon.h>
|
#include <xkbcommon/xkbcommon.h>
|
||||||
|
|
@ -161,50 +163,210 @@ get_shell(void)
|
||||||
return xstrdup(shell);
|
return xstrdup(shell);
|
||||||
}
|
}
|
||||||
|
|
||||||
static char *
|
struct config_file {
|
||||||
get_config_path_user_config(void)
|
char *path; /* Full, absolute, path */
|
||||||
|
int fd; /* FD of file, O_RDONLY */
|
||||||
|
};
|
||||||
|
|
||||||
|
struct path_component {
|
||||||
|
const char *component;
|
||||||
|
int fd;
|
||||||
|
};
|
||||||
|
typedef tll(struct path_component) path_components_t;
|
||||||
|
|
||||||
|
static void
|
||||||
|
path_component_add(path_components_t *components, const char *comp, int fd)
|
||||||
{
|
{
|
||||||
struct passwd *passwd = getpwuid(getuid());
|
assert(comp != NULL);
|
||||||
if (passwd == NULL) {
|
assert(fd >= 0);
|
||||||
LOG_ERRNO("failed to lookup user");
|
|
||||||
return NULL;
|
struct path_component pc = {.component = comp, .fd = fd};
|
||||||
|
tll_push_back(*components, pc);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
path_component_destroy(struct path_component *component)
|
||||||
|
{
|
||||||
|
assert(component->fd >= 0);
|
||||||
|
close(component->fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
path_components_destroy(path_components_t *components)
|
||||||
|
{
|
||||||
|
tll_foreach(*components, it) {
|
||||||
|
path_component_destroy(&it->item);
|
||||||
|
tll_remove(*components, it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct config_file
|
||||||
|
path_components_to_config_file(const path_components_t *components)
|
||||||
|
{
|
||||||
|
if (tll_length(*components) == 0)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
size_t len = 0;
|
||||||
|
tll_foreach(*components, it)
|
||||||
|
len += strlen(it->item.component) + 1;
|
||||||
|
|
||||||
|
char *path = malloc(len);
|
||||||
|
if (path == NULL)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
size_t idx = 0;
|
||||||
|
tll_foreach(*components, it) {
|
||||||
|
strcpy(&path[idx], it->item.component);
|
||||||
|
idx += strlen(it->item.component);
|
||||||
|
path[idx++] = '/';
|
||||||
|
}
|
||||||
|
path[idx - 1] = '\0'; /* Strip last ’/’ */
|
||||||
|
|
||||||
|
int fd_copy = dup(tll_back(*components).fd);
|
||||||
|
if (fd_copy < 0) {
|
||||||
|
free(path);
|
||||||
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *home_dir = passwd->pw_dir;
|
return (struct config_file){.path = path, .fd = fd_copy};
|
||||||
LOG_DBG("user's home directory: %s", home_dir);
|
|
||||||
|
|
||||||
char *path = xasprintf("%s/.config/footrc", home_dir);
|
err:
|
||||||
return path;
|
return (struct config_file){.path = NULL, .fd = -1};
|
||||||
}
|
}
|
||||||
|
|
||||||
static char *
|
static const char *
|
||||||
get_config_path_xdg(void)
|
get_user_home_dir(void)
|
||||||
{
|
{
|
||||||
const char *xdg_config_home = getenv("XDG_CONFIG_HOME");
|
const struct passwd *passwd = getpwuid(getuid());
|
||||||
if (xdg_config_home == NULL)
|
if (passwd == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
return passwd->pw_dir;
|
||||||
char *path = xasprintf("%s/footrc", xdg_config_home);
|
|
||||||
return path;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static char *
|
static bool
|
||||||
get_config_path(void)
|
try_open_file(path_components_t *components, const char *name)
|
||||||
{
|
{
|
||||||
|
int parent_fd = tll_back(*components).fd;
|
||||||
|
|
||||||
struct stat st;
|
struct stat st;
|
||||||
|
if (fstatat(parent_fd, name, &st, 0) == 0 && S_ISREG(st.st_mode)) {
|
||||||
|
int fd = openat(parent_fd, name, O_RDONLY);
|
||||||
|
if (fd >= 0) {
|
||||||
|
path_component_add(components, name, fd);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
char *config = get_config_path_xdg();
|
return false;
|
||||||
if (config != NULL && stat(config, &st) == 0 && S_ISREG(st.st_mode))
|
}
|
||||||
return config;
|
|
||||||
free(config);
|
|
||||||
|
|
||||||
/* 'Default' XDG_CONFIG_HOME */
|
static struct config_file
|
||||||
config = get_config_path_user_config();
|
open_config(struct config *conf)
|
||||||
if (config != NULL && stat(config, &st) == 0 && S_ISREG(st.st_mode))
|
{
|
||||||
return config;
|
struct config_file ret = {.path = NULL, .fd = -1};
|
||||||
free(config);
|
bool log_deprecation = false;
|
||||||
|
|
||||||
return NULL;
|
path_components_t components = tll_init();
|
||||||
|
|
||||||
|
const char *xdg_config_home = getenv("XDG_CONFIG_HOME");
|
||||||
|
const char *user_home_dir = get_user_home_dir();
|
||||||
|
char *xdg_config_dirs_copy = NULL;
|
||||||
|
|
||||||
|
/* Use XDG_CONFIG_HOME, or ~/.config */
|
||||||
|
if (xdg_config_home != NULL) {
|
||||||
|
int fd = open(xdg_config_home, O_RDONLY);
|
||||||
|
if (fd >= 0)
|
||||||
|
path_component_add(&components, xdg_config_home, fd);
|
||||||
|
} else if (user_home_dir != NULL) {
|
||||||
|
int home_fd = open(user_home_dir, O_RDONLY);
|
||||||
|
if (home_fd >= 0) {
|
||||||
|
int config_fd = openat(home_fd, ".config", O_RDONLY);
|
||||||
|
if (config_fd >= 0) {
|
||||||
|
path_component_add(&components, user_home_dir, home_fd);
|
||||||
|
path_component_add(&components, ".config", config_fd);
|
||||||
|
} else
|
||||||
|
close(home_fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* First look for foot/foot.ini */
|
||||||
|
if (tll_length(components) > 0) {
|
||||||
|
int foot_fd = openat(tll_back(components).fd, "foot", O_RDONLY);
|
||||||
|
if (foot_fd >= 0) {
|
||||||
|
path_component_add(&components, "foot", foot_fd);
|
||||||
|
|
||||||
|
if (try_open_file(&components, "foot.ini"))
|
||||||
|
goto done;
|
||||||
|
|
||||||
|
struct path_component pc = tll_pop_back(components);
|
||||||
|
path_component_destroy(&pc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Next try footrc */
|
||||||
|
if (tll_length(components) > 0 && try_open_file(&components, "footrc")) {
|
||||||
|
log_deprecation = true;
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Finally, try foot/foot.ini in all XDG_CONFIG_DIRS */
|
||||||
|
const char *xdg_config_dirs = getenv("XDG_CONFIG_DIRS");
|
||||||
|
xdg_config_dirs_copy = xdg_config_dirs != NULL
|
||||||
|
? strdup(xdg_config_dirs) : NULL;
|
||||||
|
|
||||||
|
if (xdg_config_dirs_copy != NULL) {
|
||||||
|
for (char *save = NULL,
|
||||||
|
*xdg_dir = strtok_r(xdg_config_dirs_copy, ":", &save);
|
||||||
|
xdg_dir != NULL;
|
||||||
|
xdg_dir = strtok_r(NULL, ":", &save))
|
||||||
|
{
|
||||||
|
path_components_destroy(&components);
|
||||||
|
|
||||||
|
int xdg_fd = open(xdg_dir, O_RDONLY);
|
||||||
|
if (xdg_fd < 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int foot_fd = openat(xdg_fd, "foot", O_RDONLY);
|
||||||
|
if (foot_fd < 0) {
|
||||||
|
close(xdg_fd);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(tll_length(components) == 0);
|
||||||
|
path_component_add(&components, xdg_dir, xdg_fd);
|
||||||
|
path_component_add(&components, "foot", foot_fd);
|
||||||
|
|
||||||
|
if (try_open_file(&components, "foot.ini"))
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out:
|
||||||
|
path_components_destroy(&components);
|
||||||
|
free(xdg_config_dirs_copy);
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
done:
|
||||||
|
assert(tll_length(components) > 0);
|
||||||
|
ret = path_components_to_config_file(&components);
|
||||||
|
|
||||||
|
if (log_deprecation && ret.path != NULL) {
|
||||||
|
LOG_WARN("deprecated: configuration in $XDG_CONFIG_HOME/footrc, "
|
||||||
|
"use $XDG_CONFIG_HOME/foot/foot.ini instead");
|
||||||
|
|
||||||
|
char *text = xstrdup(
|
||||||
|
"configuration in \033[31m$XDG_CONFIG_HOME/footrc\033[39m or "
|
||||||
|
"\033[31m~/.config/footrc\033[39m, "
|
||||||
|
"use \033[32m$XDG_CONFIG_HOME/foot/foot.ini\033[39m or "
|
||||||
|
"\033[32m~/.config/foot/foot.ini\033[39m instead");
|
||||||
|
|
||||||
|
struct user_notification deprecation = {
|
||||||
|
.kind = USER_NOTIFICATION_DEPRECATED,
|
||||||
|
.text = text,
|
||||||
|
};
|
||||||
|
tll_push_back(conf->notifications, deprecation);
|
||||||
|
}
|
||||||
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
|
|
@ -1644,24 +1806,33 @@ config_load(struct config *conf, const char *conf_path, bool errors_are_fatal)
|
||||||
add_default_search_bindings(conf);
|
add_default_search_bindings(conf);
|
||||||
add_default_mouse_bindings(conf);
|
add_default_mouse_bindings(conf);
|
||||||
|
|
||||||
char *default_path = NULL;
|
struct config_file conf_file = {.path = NULL, .fd = -1};
|
||||||
if (conf_path == NULL) {
|
if (conf_path != NULL) {
|
||||||
if ((default_path = get_config_path()) == NULL) {
|
int fd = open(conf_path, O_RDONLY);
|
||||||
/* Default conf */
|
if (fd < 0) {
|
||||||
LOG_AND_NOTIFY_WARN("no configuration found, using defaults");
|
LOG_AND_NOTIFY_ERRNO("%s: failed to open", conf_path);
|
||||||
ret = !errors_are_fatal;
|
ret = !errors_are_fatal;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
conf_path = default_path;
|
conf_file.path = xstrdup(conf_path);
|
||||||
|
conf_file.fd = fd;
|
||||||
|
} else {
|
||||||
|
conf_file = open_config(conf);
|
||||||
|
if (conf_file.fd < 0) {
|
||||||
|
LOG_AND_NOTIFY_ERR("no configuration found, using defaults");
|
||||||
|
ret = !errors_are_fatal;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(conf_path != NULL);
|
assert(conf_file.path != NULL);
|
||||||
LOG_INFO("loading configuration from %s", conf_path);
|
assert(conf_file.fd >= 0);
|
||||||
|
LOG_INFO("loading configuration from %s", conf_file.path);
|
||||||
|
|
||||||
FILE *f = fopen(conf_path, "r");
|
FILE *f = fdopen(conf_file.fd, "r");
|
||||||
if (f == NULL) {
|
if (f == NULL) {
|
||||||
LOG_AND_NOTIFY_ERR("%s: failed to open", conf_path);
|
LOG_AND_NOTIFY_ERRNO("%s: failed to open", conf_file.path);
|
||||||
ret = !errors_are_fatal;
|
ret = !errors_are_fatal;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
@ -1677,7 +1848,10 @@ out:
|
||||||
if (ret && tll_length(conf->fonts) == 0)
|
if (ret && tll_length(conf->fonts) == 0)
|
||||||
tll_push_back(conf->fonts, config_font_parse("monospace"));
|
tll_push_back(conf->fonts, config_font_parse("monospace"));
|
||||||
|
|
||||||
free(default_path);
|
free(conf_file.path);
|
||||||
|
if (conf_file.fd >= 0)
|
||||||
|
close(conf_file.fd);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,8 @@ the foot command line
|
||||||
# OPTIONS
|
# OPTIONS
|
||||||
|
|
||||||
*-c*,*--config*=_PATH_
|
*-c*,*--config*=_PATH_
|
||||||
Path to configuration file. Default: *$XDG_CONFIG_HOME/footrc*.
|
Path to configuration file. Default:
|
||||||
|
*$XDG_CONFIG_HOME/foot/foot.ini*.
|
||||||
|
|
||||||
*--check-config*
|
*--check-config*
|
||||||
Verify configuration and then exit with 0 if ok, otherwise exit
|
Verify configuration and then exit with 0 if ok, otherwise exit
|
||||||
|
|
@ -131,7 +132,7 @@ The following keyboard shortcuts are available.
|
||||||
## NORMAL MODE
|
## NORMAL MODE
|
||||||
|
|
||||||
Note that these are just the defaults; they can be changed in
|
Note that these are just the defaults; they can be changed in
|
||||||
*footrc*, see *footrc*(5).
|
*foot.ini*, see *foot.ini*(5).
|
||||||
|
|
||||||
*shift*+*page up*/*page down*
|
*shift*+*page up*/*page down*
|
||||||
Scroll up/down in history
|
Scroll up/down in history
|
||||||
|
|
@ -253,7 +254,7 @@ Finally, pressing *alt* will prefix the transmitted byte with ESC.
|
||||||
|
|
||||||
# CONFIGURATION
|
# CONFIGURATION
|
||||||
|
|
||||||
See *footrc*(5)
|
See *foot.ini*(5)
|
||||||
|
|
||||||
# BUGS
|
# BUGS
|
||||||
|
|
||||||
|
|
@ -270,4 +271,4 @@ The report should contain the following:
|
||||||
|
|
||||||
# SEE ALSO
|
# SEE ALSO
|
||||||
|
|
||||||
*footrc*(5), *footclient*(1)
|
*foot.ini*(5), *footclient*(1)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
footrc(5)
|
foot.ini(5)
|
||||||
|
|
||||||
# NAME
|
# NAME
|
||||||
footrc - configuration file
|
foot.ini - configuration file
|
||||||
|
|
||||||
# DESCRIPTION
|
# DESCRIPTION
|
||||||
|
|
||||||
|
|
@ -12,8 +12,11 @@ with a _[section]_).
|
||||||
foot will search for a configuration file in the following locations,
|
foot will search for a configuration file in the following locations,
|
||||||
in this order:
|
in this order:
|
||||||
|
|
||||||
- _XDG_CONFIG_HOME/footrc_
|
- *XDG_CONFIG_HOME/foot/foot.ini*
|
||||||
- _~/.config/footrc_
|
- *~/.config/foot/foot.ini*
|
||||||
|
- *XDG_CONFIG_HOME/footrc*
|
||||||
|
- *~/.config/footrc*
|
||||||
|
- *XDG_CONFIG_DIRS/foot/foot.init*
|
||||||
|
|
||||||
# SECTION: default
|
# SECTION: default
|
||||||
|
|
||||||
|
|
@ -3,16 +3,17 @@ sh = find_program('sh', native: true)
|
||||||
scdoc = dependency('scdoc', native: true)
|
scdoc = dependency('scdoc', native: true)
|
||||||
scdoc_prog = find_program(scdoc.get_pkgconfig_variable('scdoc'), native: true)
|
scdoc_prog = find_program(scdoc.get_pkgconfig_variable('scdoc'), native: true)
|
||||||
|
|
||||||
foreach man_src : ['foot.1.scd', 'footrc.5.scd', 'footclient.1.scd']
|
foreach man_src : [{'name': 'foot', 'section' : 1},
|
||||||
parts = man_src.split('.')
|
{'name': 'foot.ini', 'section': 5},
|
||||||
name = parts[-3]
|
{'name': 'footclient', 'section': 1}]
|
||||||
section = parts[-2]
|
name = man_src['name']
|
||||||
|
section = man_src['section']
|
||||||
out = '@0@.@1@'.format(name, section)
|
out = '@0@.@1@'.format(name, section)
|
||||||
|
|
||||||
custom_target(
|
custom_target(
|
||||||
out,
|
out,
|
||||||
output: out,
|
output: out,
|
||||||
input: man_src,
|
input: '@0@.@1@.scd'.format(name, section),
|
||||||
command: [sh, '-c', '@0@ < @INPUT@'.format(scdoc_prog.path())],
|
command: [sh, '-c', '@0@ < @INPUT@'.format(scdoc_prog.path())],
|
||||||
capture: true,
|
capture: true,
|
||||||
install: true,
|
install: true,
|
||||||
|
|
|
||||||
2
main.c
2
main.c
|
|
@ -46,7 +46,7 @@ print_usage(const char *prog_name)
|
||||||
"Usage: %s [OPTIONS...] command [ARGS...]\n"
|
"Usage: %s [OPTIONS...] command [ARGS...]\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Options:\n"
|
"Options:\n"
|
||||||
" -c,--config=PATH load configuration from PATH ($XDG_CONFIG_HOME/footrc)\n"
|
" -c,--config=PATH load configuration from PATH ($XDG_CONFIG_HOME/foot/foot.ini)\n"
|
||||||
" --check-config verify configuration, exit with 0 if ok, otherwise exit with 1\n"
|
" --check-config verify configuration, exit with 0 if ok, otherwise exit with 1\n"
|
||||||
" -f,--font=FONT comma separated list of fonts in fontconfig format (monospace)\n"
|
" -f,--font=FONT comma separated list of fonts in fontconfig format (monospace)\n"
|
||||||
" -t,--term=TERM value to set the environment variable TERM to (foot)\n"
|
" -t,--term=TERM value to set the environment variable TERM to (foot)\n"
|
||||||
|
|
|
||||||
|
|
@ -163,7 +163,7 @@ install_data(
|
||||||
install_data(
|
install_data(
|
||||||
'foot.desktop', 'foot-server.desktop',
|
'foot.desktop', 'foot-server.desktop',
|
||||||
install_dir: join_paths(get_option('datadir'), 'applications'))
|
install_dir: join_paths(get_option('datadir'), 'applications'))
|
||||||
install_data('footrc', install_dir: join_paths(get_option('datadir'), 'foot'))
|
install_data('foot.ini', install_dir: join_paths(get_option('datadir'), 'foot'))
|
||||||
|
|
||||||
subdir('completions')
|
subdir('completions')
|
||||||
subdir('doc')
|
subdir('doc')
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue