startup: look for nanorc and history files also in the XDG directories
When not finding a .nanorc file in the user's home directory, nano will look for a nanorc file in $XDG_CONFIG_HOME and in the ~/.config/nano/ fallback directory. And when not finding a .nano/ subdir in the user's home directory, nano will look for (or create) the history files in $XDG_DATA_HOME or in the ~/.local/share/nano/ fallback directory. This is a partial implementation of the XDG Base Directory Specification: https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html, for the purpose of reducing the clutter in a user's home directory, and to make it easier to back up just the configuration files. Signed-off-by: Simon Ochsenreither <simon@ochsenreither.de> Signed-off-by: Benno Schulenberg <bensberg@telfort.nl>master
parent
1463781247
commit
c16e79b612
|
@ -242,6 +242,8 @@ int interface_color_pair[] = {0};
|
||||||
|
|
||||||
char *homedir = NULL;
|
char *homedir = NULL;
|
||||||
/* The user's home directory, from $HOME or /etc/passwd. */
|
/* The user's home directory, from $HOME or /etc/passwd. */
|
||||||
|
char *statedir = NULL;
|
||||||
|
/* The directory for nano's history files. */
|
||||||
char *rcfile_with_errors = NULL;
|
char *rcfile_with_errors = NULL;
|
||||||
/* The first nanorc file, if any, that produced warnings. */
|
/* The first nanorc file, if any, that produced warnings. */
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,19 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#ifdef ENABLE_HISTORIES
|
#ifdef ENABLE_HISTORIES
|
||||||
|
|
||||||
|
#ifndef XDG_DATA_FALLBACK
|
||||||
|
#define XDG_DATA_FALLBACK "/.local/share"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef SEARCH_HISTORY
|
||||||
|
#define SEARCH_HISTORY "search_history"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef POSITION_HISTORY
|
||||||
|
#define POSITION_HISTORY "filepos_history"
|
||||||
|
#endif
|
||||||
|
|
||||||
static bool history_changed = FALSE;
|
static bool history_changed = FALSE;
|
||||||
/* Whether any of the history lists has changed. */
|
/* Whether any of the history lists has changed. */
|
||||||
|
|
||||||
|
@ -216,29 +229,6 @@ char *get_history_completion(filestruct **h, char *s, size_t len)
|
||||||
}
|
}
|
||||||
#endif /* ENSABLE_TABCOMP */
|
#endif /* ENSABLE_TABCOMP */
|
||||||
|
|
||||||
/* Return a dynamically-allocated path that is the concatenation of the
|
|
||||||
* user's home directory and the given name. */
|
|
||||||
char *construct_filename(const char *name)
|
|
||||||
{
|
|
||||||
size_t homelen = strlen(homedir);
|
|
||||||
char *joined = charalloc(homelen + strlen(name) + 1);
|
|
||||||
|
|
||||||
strcpy(joined, homedir);
|
|
||||||
strcpy(joined + homelen, name);
|
|
||||||
|
|
||||||
return joined;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *histfilename(void)
|
|
||||||
{
|
|
||||||
return construct_filename("/.nano/search_history");
|
|
||||||
}
|
|
||||||
|
|
||||||
char *poshistfilename(void)
|
|
||||||
{
|
|
||||||
return construct_filename("/.nano/filepos_history");
|
|
||||||
}
|
|
||||||
|
|
||||||
void history_error(const char *msg, ...)
|
void history_error(const char *msg, ...)
|
||||||
{
|
{
|
||||||
va_list ap;
|
va_list ap;
|
||||||
|
@ -252,38 +242,56 @@ void history_error(const char *msg, ...)
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check whether the ~/.nano subdirectory for history files exists. Return
|
/* Check whether we have or could make a directory for history files. */
|
||||||
* TRUE if it exists or was successfully created, and FALSE otherwise. */
|
bool have_statedir(void)
|
||||||
bool have_dotnano(void)
|
|
||||||
{
|
{
|
||||||
bool retval = TRUE;
|
|
||||||
struct stat dirstat;
|
struct stat dirstat;
|
||||||
char *nanodir = construct_filename("/.nano");
|
char *xdgdatadir;
|
||||||
|
|
||||||
if (stat(nanodir, &dirstat) == -1) {
|
get_homedir();
|
||||||
if (mkdir(nanodir, S_IRWXU | S_IRWXG | S_IRWXO) == -1) {
|
|
||||||
|
if (homedir != NULL) {
|
||||||
|
statedir = concatenate(homedir, "/.nano/");
|
||||||
|
|
||||||
|
if (stat(statedir, &dirstat) != 0 && S_ISDIR(dirstat.st_mode))
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(statedir);
|
||||||
|
xdgdatadir = getenv("XDG_DATA_HOME");
|
||||||
|
|
||||||
|
if (homedir == NULL && xdgdatadir == NULL)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
if (xdgdatadir != NULL) {
|
||||||
|
statedir = concatenate(xdgdatadir, "/nano/");
|
||||||
|
free(xdgdatadir);
|
||||||
|
} else
|
||||||
|
statedir = concatenate(homedir, XDG_DATA_FALLBACK "/nano/");
|
||||||
|
|
||||||
|
if (stat(statedir, &dirstat) == -1) {
|
||||||
|
if (mkdir(statedir, S_IRWXU | S_IRWXG | S_IRWXO) == -1) {
|
||||||
history_error(N_("Unable to create directory %s: %s\n"
|
history_error(N_("Unable to create directory %s: %s\n"
|
||||||
"It is required for saving/loading "
|
"It is required for saving/loading "
|
||||||
"search history or cursor positions.\n"),
|
"search history or cursor positions.\n"),
|
||||||
nanodir, strerror(errno));
|
statedir, strerror(errno));
|
||||||
retval = FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
} else if (!S_ISDIR(dirstat.st_mode)) {
|
} else if (!S_ISDIR(dirstat.st_mode)) {
|
||||||
history_error(N_("Path %s is not a directory and needs to be.\n"
|
history_error(N_("Path %s is not a directory and needs to be.\n"
|
||||||
"Nano will be unable to load or save "
|
"Nano will be unable to load or save "
|
||||||
"search history or cursor positions.\n"),
|
"search history or cursor positions.\n"),
|
||||||
nanodir);
|
statedir);
|
||||||
retval = FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
free(nanodir);
|
return TRUE;
|
||||||
return retval;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Load the histories for Search and Replace and Execute Command. */
|
/* Load the histories for Search and Replace and Execute Command. */
|
||||||
void load_history(void)
|
void load_history(void)
|
||||||
{
|
{
|
||||||
char *histname = histfilename();
|
char *histname = concatenate(statedir, SEARCH_HISTORY);
|
||||||
FILE *hisfile = fopen(histname, "rb");
|
FILE *hisfile = fopen(histname, "rb");
|
||||||
|
|
||||||
if (hisfile == NULL) {
|
if (hisfile == NULL) {
|
||||||
|
@ -356,7 +364,7 @@ void save_history(void)
|
||||||
if (!history_changed)
|
if (!history_changed)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
histname = histfilename();
|
histname = concatenate(statedir, SEARCH_HISTORY);
|
||||||
hisfile = fopen(histname, "wb");
|
hisfile = fopen(histname, "wb");
|
||||||
|
|
||||||
if (hisfile == NULL)
|
if (hisfile == NULL)
|
||||||
|
@ -381,7 +389,7 @@ void save_history(void)
|
||||||
/* Load the recorded cursor positions for files that were edited. */
|
/* Load the recorded cursor positions for files that were edited. */
|
||||||
void load_poshistory(void)
|
void load_poshistory(void)
|
||||||
{
|
{
|
||||||
char *poshist = poshistfilename();
|
char *poshist = concatenate(statedir, POSITION_HISTORY);
|
||||||
FILE *hisfile = fopen(poshist, "rb");
|
FILE *hisfile = fopen(poshist, "rb");
|
||||||
|
|
||||||
if (hisfile == NULL) {
|
if (hisfile == NULL) {
|
||||||
|
@ -447,7 +455,7 @@ void load_poshistory(void)
|
||||||
/* Save the recorded cursor positions for files that were edited. */
|
/* Save the recorded cursor positions for files that were edited. */
|
||||||
void save_poshistory(void)
|
void save_poshistory(void)
|
||||||
{
|
{
|
||||||
char *poshist = poshistfilename();
|
char *poshist = concatenate(statedir, POSITION_HISTORY);
|
||||||
poshiststruct *posptr;
|
poshiststruct *posptr;
|
||||||
FILE *hisfile = fopen(poshist, "wb");
|
FILE *hisfile = fopen(poshist, "wb");
|
||||||
|
|
||||||
|
|
|
@ -2357,8 +2357,7 @@ int main(int argc, char **argv)
|
||||||
/* If we need any of the history files, verify that the user's home
|
/* If we need any of the history files, verify that the user's home
|
||||||
* directory and its .nano subdirctory exist. */
|
* directory and its .nano subdirctory exist. */
|
||||||
if (ISSET(HISTORYLOG) || ISSET(POS_HISTORY)) {
|
if (ISSET(HISTORYLOG) || ISSET(POS_HISTORY)) {
|
||||||
get_homedir();
|
if (!have_statedir()) {
|
||||||
if (homedir == NULL || !have_dotnano()) {
|
|
||||||
UNSET(HISTORYLOG);
|
UNSET(HISTORYLOG);
|
||||||
UNSET(POS_HISTORY);
|
UNSET(POS_HISTORY);
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,6 +181,7 @@ extern char* specified_color_combo[NUMBER_OF_ELEMENTS];
|
||||||
extern int interface_color_pair[NUMBER_OF_ELEMENTS];
|
extern int interface_color_pair[NUMBER_OF_ELEMENTS];
|
||||||
|
|
||||||
extern char *homedir;
|
extern char *homedir;
|
||||||
|
extern char *statedir;
|
||||||
extern char *rcfile_with_errors;
|
extern char *rcfile_with_errors;
|
||||||
|
|
||||||
typedef void (*functionptrtype)(void);
|
typedef void (*functionptrtype)(void);
|
||||||
|
@ -365,7 +366,7 @@ void get_history_newer_void(void);
|
||||||
#ifdef ENABLE_TABCOMP
|
#ifdef ENABLE_TABCOMP
|
||||||
char *get_history_completion(filestruct **h, char *s, size_t len);
|
char *get_history_completion(filestruct **h, char *s, size_t len);
|
||||||
#endif
|
#endif
|
||||||
bool have_dotnano(void);
|
bool have_statedir(void);
|
||||||
void load_history(void);
|
void load_history(void);
|
||||||
void save_history(void);
|
void save_history(void);
|
||||||
void load_poshistory(void);
|
void load_poshistory(void);
|
||||||
|
@ -581,6 +582,7 @@ void complete_a_word(void);
|
||||||
|
|
||||||
/* All functions in utils.c. */
|
/* All functions in utils.c. */
|
||||||
void get_homedir(void);
|
void get_homedir(void);
|
||||||
|
char *concatenate(const char *path, const char *name);
|
||||||
#ifdef ENABLE_LINENUMBERS
|
#ifdef ENABLE_LINENUMBERS
|
||||||
int digits(ssize_t n);
|
int digits(ssize_t n);
|
||||||
#endif
|
#endif
|
||||||
|
|
37
src/rcfile.c
37
src/rcfile.c
|
@ -31,7 +31,7 @@
|
||||||
#ifdef ENABLE_NANORC
|
#ifdef ENABLE_NANORC
|
||||||
|
|
||||||
#ifndef RCFILE_NAME
|
#ifndef RCFILE_NAME
|
||||||
#define RCFILE_NAME ".nanorc"
|
#define RCFILE_NAME "nanorc"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static const rcoption rcopts[] = {
|
static const rcoption rcopts[] = {
|
||||||
|
@ -504,11 +504,15 @@ void parse_binding(char *ptr, bool dobind)
|
||||||
free(keycopy);
|
free(keycopy);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Verify that the given file is not a folder nor a device. */
|
/* Verify that the given file exists, is not a folder nor a device. */
|
||||||
bool is_good_file(char *file)
|
bool is_good_file(char *file)
|
||||||
{
|
{
|
||||||
struct stat rcinfo;
|
struct stat rcinfo;
|
||||||
|
|
||||||
|
/* First check that the file exists and is readable. */
|
||||||
|
if (access(file, R_OK) != 0)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
/* If the thing exists, it may not be a directory nor a device. */
|
/* If the thing exists, it may not be a directory nor a device. */
|
||||||
if (stat(file, &rcinfo) != -1 && (S_ISDIR(rcinfo.st_mode) ||
|
if (stat(file, &rcinfo) != -1 && (S_ISDIR(rcinfo.st_mode) ||
|
||||||
S_ISCHR(rcinfo.st_mode) || S_ISBLK(rcinfo.st_mode))) {
|
S_ISCHR(rcinfo.st_mode) || S_ISBLK(rcinfo.st_mode))) {
|
||||||
|
@ -1211,6 +1215,17 @@ void parse_one_nanorc(void)
|
||||||
rcfile_error(N_("Error reading %s: %s"), nanorc, strerror(errno));
|
rcfile_error(N_("Error reading %s: %s"), nanorc, strerror(errno));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool have_nanorc(char *path, char *name)
|
||||||
|
{
|
||||||
|
if (path == NULL)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
free(nanorc);
|
||||||
|
nanorc = concatenate(path, name);
|
||||||
|
|
||||||
|
return is_good_file(nanorc);
|
||||||
|
}
|
||||||
|
|
||||||
/* First read the system-wide rcfile, then the user's rcfile. */
|
/* First read the system-wide rcfile, then the user's rcfile. */
|
||||||
void do_rcfiles(void)
|
void do_rcfiles(void)
|
||||||
{
|
{
|
||||||
|
@ -1228,18 +1243,22 @@ void do_rcfiles(void)
|
||||||
|
|
||||||
get_homedir();
|
get_homedir();
|
||||||
|
|
||||||
if (homedir == NULL)
|
char *xdgconfdir = getenv("XDG_CONFIG_HOME");
|
||||||
rcfile_error(N_("I can't find my home directory! Wah!"));
|
|
||||||
else {
|
|
||||||
nanorc = charealloc(nanorc, strlen(homedir) + strlen(RCFILE_NAME) + 2);
|
|
||||||
sprintf(nanorc, "%s/%s", homedir, RCFILE_NAME);
|
|
||||||
|
|
||||||
/* Process the current user's nanorc. */
|
/* Now try the to find a nanorc file in the user's home directory
|
||||||
|
* or in the XDG configuration directories. */
|
||||||
|
if (have_nanorc(homedir, "/." RCFILE_NAME))
|
||||||
parse_one_nanorc();
|
parse_one_nanorc();
|
||||||
}
|
else if (have_nanorc(xdgconfdir, "/nano/" RCFILE_NAME))
|
||||||
|
parse_one_nanorc();
|
||||||
|
else if (have_nanorc(homedir, "/.config/nano/" RCFILE_NAME))
|
||||||
|
parse_one_nanorc();
|
||||||
|
else if (homedir == NULL && xdgconfdir == NULL)
|
||||||
|
rcfile_error(N_("I can't find my home directory! Wah!"));
|
||||||
|
|
||||||
check_vitals_mapped();
|
check_vitals_mapped();
|
||||||
|
|
||||||
|
free(xdgconfdir);
|
||||||
free(nanorc);
|
free(nanorc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
12
src/utils.c
12
src/utils.c
|
@ -53,6 +53,18 @@ void get_homedir(void)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Return a copy of the two given strings, welded together. */
|
||||||
|
char *concatenate(const char *path, const char *name)
|
||||||
|
{
|
||||||
|
size_t pathlen = strlen(path);
|
||||||
|
char *joined = charalloc(pathlen + strlen(name) + 1);
|
||||||
|
|
||||||
|
strcpy(joined, path);
|
||||||
|
strcpy(joined + pathlen, name);
|
||||||
|
|
||||||
|
return joined;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef ENABLE_LINENUMBERS
|
#ifdef ENABLE_LINENUMBERS
|
||||||
/* Return the number of digits that the given integer n takes up. */
|
/* Return the number of digits that the given integer n takes up. */
|
||||||
int digits(ssize_t n)
|
int digits(ssize_t n)
|
||||||
|
|
Loading…
Reference in New Issue