Add user patch framework (#1037)

* Add user patch framework

* Add git status patch

* Add namefirst-gitstatus compatibility

* Add patch targets

* Fix gitstatus colors and patch order
This commit is contained in:
luukvbaal 2021-06-02 06:27:06 +02:00 committed by GitHub
parent 6f862131e1
commit 93e7995ab5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 766 additions and 0 deletions

View file

@ -27,6 +27,14 @@ O_NOSSN := 0 # enable session support
O_NOUG := 0 # disable user, group name in status bar O_NOUG := 0 # disable user, group name in status bar
O_NOX11 := 0 # disable X11 integration O_NOX11 := 0 # disable X11 integration
# User patches
O_GITSTATUS := 0 # add git status to detail view
O_NAMEFIRST := 0 # print file name first, add uid and guid to detail view
ifeq ($(strip $(O_GITSTATUS)),1)
LDLIBS += -lgit2
endif
# convert targets to flags for backwards compatibility # convert targets to flags for backwards compatibility
ifneq ($(filter debug,$(MAKECMDGOALS)),) ifneq ($(filter debug,$(MAKECMDGOALS)),)
O_DEBUG := 1 O_DEBUG := 1
@ -141,10 +149,15 @@ DESKTOPFILE = misc/desktop/nnn.desktop
LOGOSVG = misc/logo/logo.svg LOGOSVG = misc/logo/logo.svg
LOGO64X64 = misc/logo/logo-64x64.png LOGO64X64 = misc/logo/logo-64x64.png
GITSTATUS = misc/patches/gitstatus
NAMEFIRST = misc/patches/namefirst
all: $(BIN) all: $(BIN)
$(BIN): $(SRC) $(HEADERS) $(BIN): $(SRC) $(HEADERS)
@$(MAKE) --silent prepatch
$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LDLIBS) $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LDLIBS)
@$(MAKE) --silent postpatch
# targets for backwards compatibility # targets for backwards compatibility
debug: $(BIN) debug: $(BIN)
@ -232,6 +245,26 @@ upload-local: sign static
clean: clean:
$(RM) -f $(BIN) nnn-$(VERSION).tar.gz *.sig $(BIN)-static $(BIN)-static-$(VERSION).x86_64.tar.gz $(BIN)-icons-static $(BIN)-icons-static-$(VERSION).x86_64.tar.gz $(BIN)-nerd-static $(BIN)-nerd-static-$(VERSION).x86_64.tar.gz $(RM) -f $(BIN) nnn-$(VERSION).tar.gz *.sig $(BIN)-static $(BIN)-static-$(VERSION).x86_64.tar.gz $(BIN)-icons-static $(BIN)-icons-static-$(VERSION).x86_64.tar.gz $(BIN)-nerd-static $(BIN)-nerd-static-$(VERSION).x86_64.tar.gz
prepatch:
ifeq ($(strip $(O_NAMEFIRST)),1)
patch --forward --strip=1 --input=$(NAMEFIRST)/mainline.diff
ifeq ($(strip $(O_GITSTATUS)),1)
patch --forward --strip=1 --input=$(GITSTATUS)/namefirst.diff
endif
else ifeq ($(strip $(O_GITSTATUS)),1)
patch --forward --strip=1 --input=$(GITSTATUS)/mainline.diff
endif
postpatch:
ifeq ($(strip $(O_NAMEFIRST)),1)
ifeq ($(strip $(O_GITSTATUS)),1)
patch --reverse --strip=1 --input=$(GITSTATUS)/namefirst.diff
endif
patch --reverse --strip=1 --input=$(NAMEFIRST)/mainline.diff
else ifeq ($(strip $(O_GITSTATUS)),1)
patch --reverse --strip=1 --input=$(GITSTATUS)/mainline.diff
endif
skip: ; skip: ;
.PHONY: all install uninstall strip static dist sign upload-local clean install-desktop uninstall-desktop .PHONY: all install uninstall strip static dist sign upload-local clean install-desktop uninstall-desktop

View file

@ -25,6 +25,14 @@ O_NOSSN := 0 # enable session support
O_NOUG := 0 # disable user, group name in status bar O_NOUG := 0 # disable user, group name in status bar
O_NOX11 := 0 # disable X11 integration O_NOX11 := 0 # disable X11 integration
# User patches
O_GITSTATUS := 0 # add git status to detail view
O_NAMEFIRST := 0 # print file name first, add uid and guid to detail view
ifeq ($(strip $(O_GITSTATUS)),1)
LDLIBS += -lgit2
endif
# convert targets to flags for backwards compatibility # convert targets to flags for backwards compatibility
ifneq ($(filter debug,$(MAKECMDGOALS)),) ifneq ($(filter debug,$(MAKECMDGOALS)),)
O_DEBUG := 1 O_DEBUG := 1
@ -143,6 +151,9 @@ HEADERS = src/nnn.h
BIN = nnn BIN = nnn
OBJS := nnn.o $(OBJS_HAIKU) OBJS := nnn.o $(OBJS_HAIKU)
GITSTATUS = misc/patches/gitstatus
NAMEFIRST = misc/patches/namefirst
all: $(BIN) all: $(BIN)
ifeq ($(shell uname -s), Haiku) ifeq ($(shell uname -s), Haiku)
@ -154,7 +165,9 @@ nnn.o: $(SRC) $(HEADERS)
$(CC) -c $(CPPFLAGS) $(CFLAGS) -o $@ $< $(CC) -c $(CPPFLAGS) $(CFLAGS) -o $@ $<
$(BIN): $(OBJS) $(BIN): $(OBJS)
@$(MAKE) --silent prepatch
$(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS) $(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
@$(MAKE) --silent postpatch
# targets for backwards compatibility # targets for backwards compatibility
debug: $(BIN) debug: $(BIN)
@ -207,6 +220,26 @@ upload-local: sign static
clean: clean:
$(RM) -f $(BIN) nnn-$(VERSION).tar.gz *.sig $(BIN)-static $(BIN)-static-$(VERSION).x86_64.tar.gz $(RM) -f $(BIN) nnn-$(VERSION).tar.gz *.sig $(BIN)-static $(BIN)-static-$(VERSION).x86_64.tar.gz
prepatch:
ifeq ($(strip $(O_NAMEFIRST)),1)
patch --forward --strip=1 --input=$(NAMEFIRST)/mainline.diff
ifeq ($(strip $(O_GITSTATUS)),1)
patch --forward --strip=1 --input=$(GITSTATUS)/namefirst.diff
endif
else ifeq ($(strip $(O_GITSTATUS)),1)
patch --forward --strip=1 --input=$(GITSTATUS)/mainline.diff
endif
postpatch:
ifeq ($(strip $(O_NAMEFIRST)),1)
ifeq ($(strip $(O_GITSTATUS)),1)
patch --reverse --strip=1 --input=$(GITSTATUS)/namefirst.diff
endif
patch --reverse --strip=1 --input=$(NAMEFIRST)/mainline.diff
else ifeq ($(strip $(O_GITSTATUS)),1)
patch --reverse --strip=1 --input=$(GITSTATUS)/mainline.diff
endif
skip: ; skip: ;
.PHONY: all install uninstall strip static dist sign upload-local clean .PHONY: all install uninstall strip static dist sign upload-local clean

14
misc/patches/README.md Normal file
View file

@ -0,0 +1,14 @@
<h1 align="center">User Patch Framework</h1>
This directory contains user submitted patches that were rejected from mainline as they tend to be more subjective in nature. The patches will be adapted on each release when necessary (v4.1 onwards). Each patch can be applied through its respective make variable during compilation. In case inter-patch merge conflicts occur, a compatability patch is provided and will automatically be applied.
## List of patches
| Patch (a-z) | Description | Make variable |
| --- | --- | --- |
| gitstatus | Add git status column to the detail view. Requires [libgit2](https://github.com/libgit2/libgit2). | O_GISTATUS |
| namefirst | Print filenames first in the detail view. Print user/group columns when a directory contains different users/groups. | O_NAMEFIRST |
To apply the patches, use the corresponding make variables, e.g.:
make O_NAMEFIRST=1

View file

@ -0,0 +1,233 @@
# Description: Add git status column to detail mode.
#
# Dependencies: libgit2
#
# Authors: @crides, Luuk van Baal
diff --git a/src/nnn.c b/src/nnn.c
index 562622f..96d946a 100644
--- a/src/nnn.c
+++ b/src/nnn.c
@@ -127,6 +127,8 @@
#include "qsort.h"
#endif
+#include <git2.h>
+
/* Macro definitions */
#define VERSION "4.0"
#define GENERAL_INFO "BSD 2-Clause\nhttps://github.com/jarun/nnn"
@@ -245,6 +247,18 @@ typedef unsigned char uchar_t;
typedef unsigned short ushort_t;
typedef unsigned long long ulong_t;
+typedef enum {
+ GIT_COLUMN_STATUS_NONE = 0,
+ GIT_COLUMN_STATUS_UNMOD,
+ GIT_COLUMN_STATUS_NEW,
+ GIT_COLUMN_STATUS_MODIFIED,
+ GIT_COLUMN_STATUS_DELETED,
+ GIT_COLUMN_STATUS_RENAMED,
+ GIT_COLUMN_STATUS_TYPE_CHANGE,
+ GIT_COLUMN_STATUS_IGNORED,
+ GIT_COLUMN_STATUS_CONFLICTED,
+} git_column_status_t;
+
/* STRUCTURES */
/* Directory entry */
@@ -258,6 +272,8 @@ typedef struct entry {
ulong_t blocks : 40; /* 5 bytes (enough for 512 TiB in 512B blocks allocated) */
ulong_t nlen : 16; /* 2 bytes (length of file name) */
ulong_t flags : 8; /* 1 byte (flags specific to the file) */
+ git_column_status_t status_indexed;
+ git_column_status_t status_staged;
};
#ifndef NOUG
uid_t uid; /* 4 bytes */
@@ -362,7 +378,18 @@ typedef struct {
} session_header_t;
#endif
+typedef struct {
+ char *path;
+ git_status_t status;
+} simple_git_status_t;
+
+typedef struct {
+ simple_git_status_t *statuses;
+ size_t len;
+} simple_git_statuses_t;
+
/* GLOBALS */
+simple_git_statuses_t git_statuses;
/* Configuration, contexts */
static settings cfg = {
@@ -809,6 +836,92 @@ static void notify_fifo(bool force);
/* Functions */
+static git_column_status_t git_get_indexed_status(const uint32_t status) {
+ if (status & GIT_STATUS_INDEX_NEW) return GIT_COLUMN_STATUS_NEW;
+ if (status & GIT_STATUS_INDEX_MODIFIED) return GIT_COLUMN_STATUS_MODIFIED;
+ if (status & GIT_STATUS_INDEX_DELETED) return GIT_COLUMN_STATUS_DELETED;
+ if (status & GIT_STATUS_INDEX_RENAMED) return GIT_COLUMN_STATUS_RENAMED;
+ if (status & GIT_STATUS_INDEX_TYPECHANGE) return GIT_COLUMN_STATUS_TYPE_CHANGE;
+ return GIT_COLUMN_STATUS_UNMOD;
+}
+
+static git_column_status_t git_get_staged_status(const uint32_t status) {
+ if (status & GIT_STATUS_WT_NEW) return GIT_COLUMN_STATUS_NEW;
+ if (status & GIT_STATUS_WT_MODIFIED) return GIT_COLUMN_STATUS_MODIFIED;
+ if (status & GIT_STATUS_WT_DELETED) return GIT_COLUMN_STATUS_DELETED;
+ if (status & GIT_STATUS_WT_RENAMED) return GIT_COLUMN_STATUS_RENAMED;
+ if (status & GIT_STATUS_WT_TYPECHANGE) return GIT_COLUMN_STATUS_TYPE_CHANGE;
+ if (status & GIT_STATUS_IGNORED) return GIT_COLUMN_STATUS_IGNORED;
+ if (status & GIT_STATUS_CONFLICTED) return GIT_COLUMN_STATUS_CONFLICTED;
+ return GIT_COLUMN_STATUS_UNMOD;
+}
+
+static void print_gitstatus(git_column_status_t status) {
+ switch (status) {
+ case GIT_COLUMN_STATUS_NONE: break;
+ case GIT_COLUMN_STATUS_UNMOD: addch('-' | 0); break;
+ case GIT_COLUMN_STATUS_NEW: addch('N' | (g_state.oldcolor ? COLOR_PAIR(1) : COLOR_PAIR(C_EXE))); break;
+ case GIT_COLUMN_STATUS_MODIFIED: addch('M' | (g_state.oldcolor ? COLOR_PAIR(1) : COLOR_PAIR(4))); break;
+ case GIT_COLUMN_STATUS_DELETED: addch('D' | (g_state.oldcolor ? COLOR_PAIR(1) : COLOR_PAIR(C_UND))); break;
+ case GIT_COLUMN_STATUS_RENAMED: addch('R' | (g_state.oldcolor ? COLOR_PAIR(1) : COLOR_PAIR(C_CHR))); break;
+ case GIT_COLUMN_STATUS_TYPE_CHANGE: addch('T' | (g_state.oldcolor ? COLOR_PAIR(1) : COLOR_PAIR(C_HRD))); break;
+ case GIT_COLUMN_STATUS_IGNORED: addch('I' | 0); break;
+ case GIT_COLUMN_STATUS_CONFLICTED: addch('U' | (g_state.oldcolor ? COLOR_PAIR(1) : COLOR_PAIR(C_UND))); break;
+ }
+}
+
+static bool starts_with(const char *const s, const char *const start) {
+ const size_t start_len = strlen(start);
+
+ return (start_len <= strlen(s)) && (0 == strncmp(s, start, start_len));
+}
+
+static size_t mkpath(const char *dir, const char *name, char *out);
+static simple_git_statuses_t statuses_from_path(const char *path) {
+ simple_git_statuses_t statuses = { .statuses = NULL, .len = 0 };
+ git_buf ret = { .ptr = 0, .asize = 0, .size = 0 };
+ git_repository *repo;
+ git_repository_discover(&ret, path, false, NULL);
+ git_repository_open(&repo, ret.ptr);
+ git_buf_dispose(&ret);
+
+ if (repo) {
+ git_status_list *status_list = NULL;
+ git_status_list_new(&status_list, repo, NULL);
+ statuses.len = git_status_list_entrycount(status_list);
+ statuses.statuses = malloc(statuses.len * sizeof(simple_git_status_t));
+ const char *workdir = git_repository_workdir(repo);
+
+ for (size_t i = 0; i < statuses.len; i ++) {
+ const git_status_entry *status_ent = git_status_byindex(status_list, i);
+ const char *entry_path = status_ent->head_to_index ? status_ent->head_to_index->old_file.path
+ : status_ent->index_to_workdir->old_file.path;
+ char *joined = malloc(PATH_MAX * sizeof(char));
+ mkpath(workdir, entry_path, joined);
+ char *canon_path = realpath(joined, NULL);
+
+ if (canon_path) {
+ statuses.statuses[i].path = canon_path;
+ free(joined);
+ } else
+ statuses.statuses[i].path = joined;
+
+ statuses.statuses[i].status = status_ent->status;
+ }
+
+ git_status_list_free(status_list);
+ git_repository_free(repo);
+ }
+ return statuses;
+}
+
+static void statuses_free(simple_git_statuses_t statuses) {
+ for (size_t i = 0; i < statuses.len; i ++)
+ free(statuses.statuses[i].path);
+
+ free(statuses.statuses);
+}
+
static void sigint_handler(int UNUSED(sig))
{
g_state.interrupt = 1;
@@ -3794,6 +3907,12 @@ static void printent(const struct entry *ent, uint_t namecols, bool sel)
if (attrs)
attroff(attrs);
+
+ if (ent->status_indexed != GIT_COLUMN_STATUS_NONE)
+ addch(' ');
+
+ print_gitstatus(ent->status_indexed);
+ print_gitstatus(ent->status_staged);
}
attrs = 0;
@@ -3809,6 +3928,7 @@ static void printent(const struct entry *ent, uint_t namecols, bool sel)
color_pair = C_MIS;
if (color_pair && fcolors[color_pair])
attrs |= COLOR_PAIR(color_pair);
+
#ifdef ICONS_ENABLED
print_icon(ent, attrs);
#endif
@@ -5088,6 +5208,9 @@ static int dentfill(char *path, struct entry **ppdents)
struct stat sb_path, sb;
DIR *dirp = opendir(path);
+ statuses_free(git_statuses);
+ git_statuses = statuses_from_path(path);
+
ndents = 0;
DPRINTF_S(__func__);
@@ -5277,6 +5400,26 @@ static int dentfill(char *path, struct entry **ppdents)
dentp->gid = sb.st_gid;
#endif
+ if (git_statuses.len) {
+ uint32_t merged_status = 0;
+ char joined[PATH_MAX];
+ mkpath(path, dentp->name, joined);
+ char *real = realpath(joined, NULL);
+ char *canon_path = real ? real : joined;
+
+ for (size_t i = 0; i < git_statuses.len; i ++)
+ if (((dentp->mode & S_IFMT) == S_IFDIR) ? starts_with(git_statuses.statuses[i].path, canon_path) :
+ !xstrcmp(git_statuses.statuses[i].path, canon_path) || starts_with(canon_path, git_statuses.statuses[i].path))
+ merged_status |= git_statuses.statuses[i].status;
+
+ dentp->status_indexed = git_get_indexed_status(merged_status);
+ dentp->status_staged = git_get_staged_status(merged_status);
+ free(real);
+ } else {
+ dentp->status_indexed = GIT_COLUMN_STATUS_NONE;
+ dentp->status_staged = GIT_COLUMN_STATUS_NONE;
+ }
+
dentp->flags = S_ISDIR(sb.st_mode) ? 0 : ((sb.st_nlink > 1) ? HARD_LINK : 0);
if (entflags) {
dentp->flags |= entflags;
@@ -7712,6 +7855,8 @@ static void cleanup(void)
fflush(stdout);
}
#endif
+ statuses_free(git_statuses);
+ git_libgit2_shutdown();
free(selpath);
free(plgpath);
free(cfgpath);
@@ -7907,6 +8052,7 @@ int main(int argc, char *argv[])
return EXIT_FAILURE;
atexit(cleanup);
+ git_libgit2_init();
/* Check if we are in path list mode */
if (!isatty(STDIN_FILENO)) {

View file

@ -0,0 +1,226 @@
# Description: Add git status column to detail mode.
# Compatibility patch for the namefirst patch.
#
# Dependencies: libgit2
#
# Authors: @crides, Luuk van Baaldiff --git a/src/nnn.c b/src/nnn.c
index d66e5da..5770f1a 100644
--- a/src/nnn.c
+++ b/src/nnn.c
@@ -127,6 +127,8 @@
#include "qsort.h"
#endif
+#include <git2.h>
+
/* Macro definitions */
#define VERSION "4.0"
#define GENERAL_INFO "BSD 2-Clause\nhttps://github.com/jarun/nnn"
@@ -245,6 +247,18 @@ typedef unsigned char uchar_t;
typedef unsigned short ushort_t;
typedef unsigned long long ulong_t;
+typedef enum {
+ GIT_COLUMN_STATUS_NONE = 0,
+ GIT_COLUMN_STATUS_UNMOD,
+ GIT_COLUMN_STATUS_NEW,
+ GIT_COLUMN_STATUS_MODIFIED,
+ GIT_COLUMN_STATUS_DELETED,
+ GIT_COLUMN_STATUS_RENAMED,
+ GIT_COLUMN_STATUS_TYPE_CHANGE,
+ GIT_COLUMN_STATUS_IGNORED,
+ GIT_COLUMN_STATUS_CONFLICTED,
+} git_column_status_t;
+
/* STRUCTURES */
/* Directory entry */
@@ -258,6 +272,8 @@ typedef struct entry {
ulong_t blocks : 40; /* 5 bytes (enough for 512 TiB in 512B blocks allocated) */
ulong_t nlen : 16; /* 2 bytes (length of file name) */
ulong_t flags : 8; /* 1 byte (flags specific to the file) */
+ git_column_status_t status_indexed;
+ git_column_status_t status_staged;
};
#ifndef NOUG
uid_t uid; /* 4 bytes */
@@ -366,7 +382,18 @@ static struct {
ushort_t maxnameln, maxsizeln, maxuidln, maxgidln, maxentln, uidln, gidln, printguid;
} dtls;
+typedef struct {
+ char *path;
+ git_status_t status;
+} simple_git_status_t;
+
+typedef struct {
+ simple_git_status_t *statuses;
+ size_t len;
+} simple_git_statuses_t;
+
/* GLOBALS */
+simple_git_statuses_t git_statuses;
/* Configuration, contexts */
static settings cfg = {
@@ -813,6 +840,92 @@ static void notify_fifo(bool force);
/* Functions */
+static git_column_status_t git_get_indexed_status(const uint32_t status) {
+ if (status & GIT_STATUS_INDEX_NEW) return GIT_COLUMN_STATUS_NEW;
+ if (status & GIT_STATUS_INDEX_MODIFIED) return GIT_COLUMN_STATUS_MODIFIED;
+ if (status & GIT_STATUS_INDEX_DELETED) return GIT_COLUMN_STATUS_DELETED;
+ if (status & GIT_STATUS_INDEX_RENAMED) return GIT_COLUMN_STATUS_RENAMED;
+ if (status & GIT_STATUS_INDEX_TYPECHANGE) return GIT_COLUMN_STATUS_TYPE_CHANGE;
+ return GIT_COLUMN_STATUS_UNMOD;
+}
+
+static git_column_status_t git_get_staged_status(const uint32_t status) {
+ if (status & GIT_STATUS_WT_NEW) return GIT_COLUMN_STATUS_NEW;
+ if (status & GIT_STATUS_WT_MODIFIED) return GIT_COLUMN_STATUS_MODIFIED;
+ if (status & GIT_STATUS_WT_DELETED) return GIT_COLUMN_STATUS_DELETED;
+ if (status & GIT_STATUS_WT_RENAMED) return GIT_COLUMN_STATUS_RENAMED;
+ if (status & GIT_STATUS_WT_TYPECHANGE) return GIT_COLUMN_STATUS_TYPE_CHANGE;
+ if (status & GIT_STATUS_IGNORED) return GIT_COLUMN_STATUS_IGNORED;
+ if (status & GIT_STATUS_CONFLICTED) return GIT_COLUMN_STATUS_CONFLICTED;
+ return GIT_COLUMN_STATUS_UNMOD;
+}
+
+static void print_gitstatus(git_column_status_t status) {
+ switch (status) {
+ case GIT_COLUMN_STATUS_NONE: break;
+ case GIT_COLUMN_STATUS_UNMOD: addch('-' | 0); break;
+ case GIT_COLUMN_STATUS_NEW: addch('N' | (g_state.oldcolor ? COLOR_PAIR(1) : COLOR_PAIR(C_EXE))); break;
+ case GIT_COLUMN_STATUS_MODIFIED: addch('M' | (g_state.oldcolor ? COLOR_PAIR(1) : COLOR_PAIR(4))); break;
+ case GIT_COLUMN_STATUS_DELETED: addch('D' | (g_state.oldcolor ? COLOR_PAIR(1) : COLOR_PAIR(C_UND))); break;
+ case GIT_COLUMN_STATUS_RENAMED: addch('R' | (g_state.oldcolor ? COLOR_PAIR(1) : COLOR_PAIR(C_CHR))); break;
+ case GIT_COLUMN_STATUS_TYPE_CHANGE: addch('T' | (g_state.oldcolor ? COLOR_PAIR(1) : COLOR_PAIR(C_HRD))); break;
+ case GIT_COLUMN_STATUS_IGNORED: addch('I' | 0); break;
+ case GIT_COLUMN_STATUS_CONFLICTED: addch('U' | (g_state.oldcolor ? COLOR_PAIR(1) : COLOR_PAIR(C_UND))); break;
+ }
+}
+
+static bool starts_with(const char *const s, const char *const start) {
+ const size_t start_len = strlen(start);
+
+ return (start_len <= strlen(s)) && (0 == strncmp(s, start, start_len));
+}
+
+static size_t mkpath(const char *dir, const char *name, char *out);
+static simple_git_statuses_t statuses_from_path(const char *path) {
+ simple_git_statuses_t statuses = { .statuses = NULL, .len = 0 };
+ git_buf ret = { .ptr = 0, .asize = 0, .size = 0 };
+ git_repository *repo;
+ git_repository_discover(&ret, path, false, NULL);
+ git_repository_open(&repo, ret.ptr);
+ git_buf_dispose(&ret);
+
+ if (repo) {
+ git_status_list *status_list = NULL;
+ git_status_list_new(&status_list, repo, NULL);
+ statuses.len = git_status_list_entrycount(status_list);
+ statuses.statuses = malloc(statuses.len * sizeof(simple_git_status_t));
+ const char *workdir = git_repository_workdir(repo);
+
+ for (size_t i = 0; i < statuses.len; i ++) {
+ const git_status_entry *status_ent = git_status_byindex(status_list, i);
+ const char *entry_path = status_ent->head_to_index ? status_ent->head_to_index->old_file.path
+ : status_ent->index_to_workdir->old_file.path;
+ char *joined = malloc(PATH_MAX * sizeof(char));
+ mkpath(workdir, entry_path, joined);
+ char *canon_path = realpath(joined, NULL);
+
+ if (canon_path) {
+ statuses.statuses[i].path = canon_path;
+ free(joined);
+ } else
+ statuses.statuses[i].path = joined;
+
+ statuses.statuses[i].status = status_ent->status;
+ }
+
+ git_status_list_free(status_list);
+ git_repository_free(repo);
+ }
+ return statuses;
+}
+
+static void statuses_free(simple_git_statuses_t statuses) {
+ for (size_t i = 0; i < statuses.len; i ++)
+ free(statuses.statuses[i].path);
+
+ free(statuses.statuses);
+}
+
static void sigint_handler(int UNUSED(sig))
{
g_state.interrupt = 1;
@@ -3783,6 +3896,13 @@ static void printent(const struct entry *ent, uint_t namecols, bool sel)
addch((ent->flags & FILE_SELECTED) ? '+' | A_REVERSE | A_BOLD : ' ');
+ if (cfg.showdetail) {
+ print_gitstatus(ent->status_indexed);
+ print_gitstatus(ent->status_staged);
+ if (ent->status_indexed != GIT_COLUMN_STATUS_NONE)
+ addch(' ');
+ }
+
if (g_state.oldcolor)
resetdircolor(ent->flags);
else {
@@ -5094,6 +5214,9 @@ static int dentfill(char *path, struct entry **ppdents)
struct stat sb_path, sb;
DIR *dirp = opendir(path);
+ statuses_free(git_statuses);
+ git_statuses = statuses_from_path(path);
+
ndents = 0;
DPRINTF_S(__func__);
@@ -5283,6 +5406,26 @@ static int dentfill(char *path, struct entry **ppdents)
dentp->gid = sb.st_gid;
#endif
+ if (git_statuses.len) {
+ uint32_t merged_status = 0;
+ char joined[PATH_MAX];
+ mkpath(path, dentp->name, joined);
+ char *real = realpath(joined, NULL);
+ char *canon_path = real ? real : joined;
+
+ for (size_t i = 0; i < git_statuses.len; i ++)
+ if (((dentp->mode & S_IFMT) == S_IFDIR) ? starts_with(git_statuses.statuses[i].path, canon_path) :
+ !xstrcmp(git_statuses.statuses[i].path, canon_path) || starts_with(canon_path, git_statuses.statuses[i].path))
+ merged_status |= git_statuses.statuses[i].status;
+
+ dentp->status_indexed = git_get_indexed_status(merged_status);
+ dentp->status_staged = git_get_staged_status(merged_status);
+ free(real);
+ } else {
+ dentp->status_indexed = GIT_COLUMN_STATUS_NONE;
+ dentp->status_staged = GIT_COLUMN_STATUS_NONE;
+ }
+
dentp->flags = S_ISDIR(sb.st_mode) ? 0 : ((sb.st_nlink > 1) ? HARD_LINK : 0);
if (entflags) {
dentp->flags |= entflags;
@@ -7714,6 +7857,8 @@ static void cleanup(void)
fflush(stdout);
}
#endif
+ statuses_free(git_statuses);
+ git_libgit2_shutdown();
free(selpath);
free(plgpath);
free(cfgpath);
@@ -7909,6 +8054,7 @@ int main(int argc, char *argv[])
return EXIT_FAILURE;
atexit(cleanup);
+ git_libgit2_init();
/* Check if we are in path list mode */
if (!isatty(STDIN_FILENO)) {

View file

@ -0,0 +1,227 @@
# Description: Prints filenames first in the detail view. Prints user/group
# columns when a directory contains different users/groups.
#
# Author: Luuk van Baal
diff --git a/src/nnn.c b/src/nnn.c
index 562622fa..d66e5dac 100644
--- a/src/nnn.c
+++ b/src/nnn.c
@@ -362,6 +362,10 @@ typedef struct {
} session_header_t;
#endif
+static struct {
+ ushort_t maxnameln, maxsizeln, maxuidln, maxgidln, maxentln, uidln, gidln, printguid;
+} dtls;
+
/* GLOBALS */
/* Configuration, contexts */
@@ -1038,10 +1042,12 @@ static char *getpwname(uid_t uid)
static char *namecache = NULL;
if (uidcache != uid) {
+ if (dtls.maxuidln && !dtls.printguid) dtls.printguid = 1;
struct passwd *pw = getpwuid(uid);
uidcache = uid;
namecache = pw ? pw->pw_name : NULL;
+ dtls.uidln = xstrlen(namecache ? namecache : xitoa(uid));
}
return namecache ? namecache : xitoa(uid);
@@ -1053,10 +1059,12 @@ static char *getgrname(gid_t gid)
static char *grpcache = NULL;
if (gidcache != gid) {
+ if (dtls.maxgidln && !dtls.printguid) dtls.printguid = 1;
struct group *gr = getgrgid(gid);
gidcache = gid;
grpcache = gr ? gr->gr_name : NULL;
+ dtls.gidln = xstrlen(grpcache ? grpcache : xitoa(gid));
}
return grpcache ? grpcache : xitoa(gid);
@@ -3479,14 +3487,13 @@ static void resetdircolor(int flags)
* Max supported str length: NAME_MAX;
*/
#ifdef NOLC
-static char *unescape(const char *str, uint_t maxcols)
+static size_t unescape(const char *str, uint_t maxcols)
{
char * const wbuf = g_buf;
char *buf = wbuf;
-
- xstrsncpy(wbuf, str, maxcols);
+ size_t len = xstrsncpy(wbuf, str, maxcols);
#else
-static wchar_t *unescape(const char *str, uint_t maxcols)
+static size_t unescape(const char *str, uint_t maxcols)
{
wchar_t * const wbuf = (wchar_t *)g_buf;
wchar_t *buf = wbuf;
@@ -3510,7 +3517,7 @@ static wchar_t *unescape(const char *str, uint_t maxcols)
++buf;
}
- return wbuf;
+ return len;
}
static off_t get_size(off_t size, off_t *pval, uint_t comp)
@@ -3771,33 +3778,7 @@ static uchar_t get_color_pair_name_ind(const struct entry *ent, char *pind, int
static void printent(const struct entry *ent, uint_t namecols, bool sel)
{
char ind = '\0';
- int attrs;
-
- if (cfg.showdetail) {
- int type = ent->mode & S_IFMT;
- char perms[6] = {' ', ' ', (char)('0' + ((ent->mode >> 6) & 7)),
- (char)('0' + ((ent->mode >> 3) & 7)),
- (char)('0' + (ent->mode & 7)), '\0'};
-
- addch(' ');
- attrs = g_state.oldcolor ? (resetdircolor(ent->flags), A_DIM)
- : (fcolors[C_MIS] ? COLOR_PAIR(C_MIS) : 0);
- if (attrs)
- attron(attrs);
-
- /* Print details */
- print_time(&ent->sec);
-
- printw("%s%9s ", perms, (type == S_IFREG || type == S_IFDIR)
- ? coolsize(cfg.blkorder ? (blkcnt_t)ent->blocks << blk_shift : ent->size)
- : (type = (uchar_t)get_detail_ind(ent->mode), (char *)&type));
-
- if (attrs)
- attroff(attrs);
- }
-
- attrs = 0;
-
+ int attrs = 0, namelen;
uchar_t color_pair = get_color_pair_name_ind(ent, &ind, &attrs);
addch((ent->flags & FILE_SELECTED) ? '+' | A_REVERSE | A_BOLD : ' ');
@@ -3822,15 +3803,40 @@ static void printent(const struct entry *ent, uint_t namecols, bool sel)
++namecols;
#ifndef NOLC
- addwstr(unescape(ent->name, namecols));
+ addwstr((namelen = unescape(ent->name, namecols), (wchar_t *)g_buf));
#else
- addstr(unescape(ent->name, MIN(namecols, ent->nlen) + 1));
+ addstr((namelen = unescape(ent->name, MIN(namecols, ent->nlen) + 1), (char *)g_buf));
#endif
- if (attrs)
+ if (!sel && attrs)
attroff(attrs);
if (ind)
addch(ind);
+ if (cfg.showdetail) {
+ int type = ent->mode & S_IFMT;
+ char perms[6] = {(char)('0' + ((ent->mode >> 6) & 7)),
+ (char)('0' + ((ent->mode >> 3) & 7)),
+ (char)('0' + (ent->mode & 7)), ' ', ' ', '\0'}, *size = NULL;
+
+ if (attrs)
+ attron(attrs);
+ if (!g_state.oldcolor && (type == S_IFDIR || (type == S_IFLNK && ent->flags & DIR_OR_LINK_TO_DIR)))
+ attroff(A_BOLD);
+ size_t sizelen = (type == S_IFREG || type == S_IFDIR) ? xstrlen(size = coolsize(cfg.blkorder ? ent->blocks << blk_shift : ent->size)) : 1;
+ printw("%*c%*s%s%s", 1 + MIN(namecols, dtls.maxnameln + (size_t)(ind ? 0 : 1)) - namelen, ' ',
+ dtls.maxsizeln - sizelen, "", size ? size : (type = (uchar_t)get_detail_ind(ent->mode), (char *)&type), " ");
+#ifndef NOUG
+ if (g_state.uidgid && dtls.printguid) {
+ addstr(getpwname(ent->uid));
+ printw("%*c%s", dtls.maxuidln + 1 - dtls.uidln, ' ', getgrname(ent->gid));
+ printw("%*c", dtls.maxgidln + 2 - dtls.gidln, ' ');
+ }
+#endif
+ addstr(perms);
+ print_time(&ent->sec);
+ }
+ if (attrs)
+ attroff(attrs);
}
static void savecurctx(settings *curcfg, char *path, char *curname, int nextctx)
@@ -5814,18 +5820,6 @@ static void statusbar(char *path)
tocursor();
}
-static inline void markhovered(void)
-{
- if (cfg.showdetail && ndents) { /* Reversed block for hovered entry */
- tocursor();
-#ifdef ICONS_ENABLED
- addstr(MD_ARROW_FORWARD);
-#else
- addch(' ' | A_REVERSE);
-#endif
- }
-}
-
static int adjust_cols(int n)
{
/* Calculate the number of cols available to print entry name */
@@ -5833,13 +5827,10 @@ static int adjust_cols(int n)
n -= (g_state.oldcolor ? 0 : 1 + xstrlen(ICON_PADDING_LEFT) + xstrlen(ICON_PADDING_RIGHT));
#endif
if (cfg.showdetail) {
- /* Fallback to light mode if less than 35 columns */
- if (n < 36)
+ if (n < (dtls.maxentln + 1 - dtls.maxnameln))
cfg.showdetail ^= 1;
- else {
- /* 3 more accounted for below */
- n -= 32;
- }
+ else
+ n -= (dtls.maxentln - 2 - dtls.maxnameln);
}
/* 2 columns for preceding space and indicator */
@@ -5877,8 +5868,6 @@ static void draw_line(char *path, int ncols)
if (dir)
attroff(COLOR_PAIR(cfg.curctx + 1) | A_BOLD);
- markhovered();
-
statusbar(path);
}
@@ -5970,6 +5959,21 @@ static void redraw(char *path)
attroff(A_UNDERLINE | COLOR_PAIR(cfg.curctx + 1));
+ if (cfg.showdetail) {
+ ushort_t lenbuf = dtls.maxnameln = dtls.maxsizeln = dtls.maxuidln = dtls.maxgidln = dtls.printguid = 0;
+ for (i = curscroll; i < ndents && i < curscroll + onscreen; ++i) {
+ if ((lenbuf = pdents[i].nlen - 1) > dtls.maxnameln) dtls.maxnameln = lenbuf;
+ if ((lenbuf = xstrlen(coolsize(cfg.blkorder ? pdents[i].blocks << blk_shift : pdents[i].size))) > dtls.maxsizeln) dtls.maxsizeln = lenbuf;
+#ifndef NOUG
+ if (g_state.uidgid) {
+ if ((getpwname(pdents[i].uid), dtls.uidln) > dtls.maxuidln) dtls.maxuidln = dtls.uidln;
+ if ((getgrname(pdents[i].gid), dtls.gidln) > dtls.maxgidln) dtls.maxgidln = dtls.gidln;
+ }
+#endif
+ }
+ }
+ dtls.maxentln = dtls.maxnameln + dtls.maxsizeln + dtls.maxuidln + dtls.maxgidln + (g_state.uidgid ? 29 : 26);
+
ncols = adjust_cols(ncols);
/* Go to first entry */
@@ -6011,8 +6015,6 @@ static void redraw(char *path)
#endif
}
- markhovered();
-
statusbar(path);
}