]> Sergey Matveev's repositories - nnn.git/blob - patches/gitstatus/mainline.diff
Fix patch build errors
[nnn.git] / patches / gitstatus / mainline.diff
1 # Description: Add git status column to detail mode. Provides additional
2 #              command line flag -G which will render the git status
3 #              column also in normal mode. Vim plugin users may consider
4 #              adding the -G flag to their command override.
5 #
6 # Authors: Luuk van Baal
7
8 diff --git a/src/nnn.c b/src/nnn.c
9 index 83ecdb90..4397944a 100644
10 --- a/src/nnn.c
11 +++ b/src/nnn.c
12 @@ -270,6 +270,25 @@
13  #define VFS_USED  1
14  #define VFS_SIZE  2
15
16 +/* Git icons */
17 +#ifdef NERD
18 +#define GIT_ADD ""
19 +#define GIT_DEL ""
20 +#define GIT_IGN ""
21 +#define GIT_MOD ""
22 +#define GIT_NEW ""
23 +#define GIT_NON "-"
24 +#define GIT_UPD "󰚰"
25 +#else
26 +#define GIT_ADD "A"
27 +#define GIT_DEL "D"
28 +#define GIT_IGN "!"
29 +#define GIT_MOD "M"
30 +#define GIT_NEW "?"
31 +#define GIT_NON "-"
32 +#define GIT_UPD "U"
33 +#endif
34 +
35  /* TYPE DEFINITIONS */
36  typedef unsigned int uint_t;
37  typedef unsigned char uchar_t;
38 @@ -294,6 +313,7 @@ typedef struct entry {
39         uid_t uid; /* 4 bytes */
40         gid_t gid; /* 4 bytes */
41  #endif
42 +       char git_status[2][5];
43  } *pEntry;
44
45  /* Selection marker */
46 @@ -349,6 +369,7 @@ typedef struct {
47         uint_t cliopener  : 1;  /* All-CLI app opener */
48         uint_t waitedit   : 1;  /* For ops that can't be detached, used EDITOR */
49         uint_t rollover   : 1;  /* Roll over at edges */
50 +       uint_t normalgit  : 1;  /* Show git status in normal mode */
51  } settings;
52
53  /* Non-persistent program-internal states (alphabeical order) */
54 @@ -400,7 +421,17 @@ typedef struct {
55  } session_header_t;
56  #endif
57
58 +typedef struct {
59 +       char status[2];
60 +       char path[PATH_MAX];
61 +} git_status_t;
62 +
63  /* GLOBALS */
64 +struct {
65 +       bool show;
66 +       size_t len;
67 +       git_status_t *statuses;
68 +} git_statuses;
69
70  /* Configuration, contexts */
71  static settings cfg = {
72 @@ -3796,6 +3827,47 @@ static int get_kv_key(kv *kvarr, char *val, uchar_t max, uchar_t id)
73         return -1;
74  }
75
76 +static size_t get_git_statuses(const char *path)
77 +{
78 +       static char gst[] = "git -c core.quotePath= status -s --no-renames --ignored=matching -unormal . 2>/dev/null";
79 +       FILE *fp = popen(gst, "r");
80 +       char status[PATH_MAX];
81 +       size_t pathindex, i = -1;
82 +       git_statuses.show = FALSE;
83 +
84 +       while (fgets(status, PATH_MAX, fp)) {
85 +               pathindex = (status[3] == '"') ? 4 : 3;
86 +               if (!cfg.showhidden && status[pathindex] == '.')
87 +                       continue;
88 +               status[xstrlen(status) - pathindex + 2] = '\0';
89 +               git_statuses.statuses = xrealloc(git_statuses.statuses, sizeof(git_status_t) * (++i + 1));
90 +               git_statuses.statuses[i].status[0] = status[0];
91 +               git_statuses.statuses[i].status[1] = status[1];
92 +               mkpath(path, status + pathindex, git_statuses.statuses[i].path);
93 +       }
94 +
95 +       pclose(fp);
96 +       return (i + 1);
97 +}
98 +
99 +static void set_git_status(char status[][5], uint_t nr)
100 +{
101 +       for (int j = 0; j < 2; j++) {
102 +               if (status[j][0] == '-')
103 +                       switch (git_statuses.statuses[nr].status[j]) {
104 +                               case ' ': xstrsncpy(status[j], GIT_NON, 4); break;
105 +                               case 'M': xstrsncpy(status[j], GIT_MOD, 4); break;
106 +                               case 'A': xstrsncpy(status[j], GIT_ADD, 4); break;
107 +                               case '?': xstrsncpy(status[j], GIT_NEW, 4); break;
108 +                               case '!': xstrsncpy(status[j], GIT_IGN, 4); break;
109 +                               case 'D': xstrsncpy(status[j], GIT_DEL, 4); break;
110 +                               case 'U': xstrsncpy(status[j], GIT_UPD, 4); break;
111 +                       }
112 +       }
113 +       if (git_statuses.statuses[nr].status[1] != '!')
114 +               git_statuses.show = TRUE;
115 +}
116 +
117  static void resetdircolor(int flags)
118  {
119         /* Directories are always shown on top, clear the color when moving to first file */
120 @@ -4123,6 +4195,10 @@ static void printent(const struct entry *ent, uint_t namecols, bool sel)
121
122         uchar_t color_pair = get_color_pair_name_ind(ent, &ind, &attrs);
123
124 +       if (git_statuses.show && (cfg.showdetail || cfg.normalgit))
125 +               printw("%*s%s%s", (cfg.normalgit && !cfg.showdetail) ? 1 : 0, "",
126 +                               ent->git_status[0], ent->git_status[1]);
127 +
128         addch((ent->flags & FILE_SELECTED) ? '+' | A_REVERSE | A_BOLD : ' ');
129
130         if (g_state.oldcolor)
131 @@ -5592,6 +5668,11 @@ static int dentfill(char *path, struct entry **ppdents)
132                 attron(COLOR_PAIR(cfg.curctx + 1));
133         }
134
135 +       char linkpath[PATH_MAX];
136 +       if ((git_statuses.len = get_git_statuses(path)))
137 +               if (!realpath(path, linkpath))
138 +                       printwarn(NULL);
139 +
140  #if _POSIX_C_SOURCE >= 200112L
141         posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL);
142  #endif
143 @@ -5792,6 +5873,29 @@ static int dentfill(char *path, struct entry **ppdents)
144  #endif
145                 }
146
147 +               if (git_statuses.len) {
148 +                       char dentpath[PATH_MAX];
149 +                       size_t pathlen = mkpath(linkpath, dentp->name, dentpath);
150 +                       dentp->git_status[0][0] = dentp->git_status[1][0] = '-';
151 +                       dentp->git_status[0][1] = dentp->git_status[1][1] = '\0';
152 +
153 +                       if (dentp->flags & DIR_OR_DIRLNK) {
154 +                               char prefix[PATH_MAX];
155 +                               memccpy(prefix, dentpath, '\0', PATH_MAX);
156 +                               prefix[pathlen - 1] = '/';
157 +
158 +                               for (size_t i = 0; i < git_statuses.len; ++i)
159 +                                       if (is_prefix(git_statuses.statuses[i].path, prefix, pathlen))
160 +                                               set_git_status(dentp->git_status, i);
161 +                       } else {
162 +                               for (size_t i = 0; i < git_statuses.len; ++i)
163 +                                       if (!xstrcmp(git_statuses.statuses[i].path, dentpath)) {
164 +                                               set_git_status(dentp->git_status, i);
165 +                                               break;
166 +                                       }
167 +                       }
168 +               }
169 +
170                 ++ndents;
171         } while ((dp = readdir(dirp)));
172
173 @@ -6361,11 +6465,12 @@ static int adjust_cols(int n)
174  #endif
175         if (cfg.showdetail) {
176                 /* Fallback to light mode if less than 35 columns */
177 -               if (n < 36)
178 +               if (n < 38)
179                         cfg.showdetail ^= 1;
180                 else /* 2 more accounted for below */
181 -                       n -= 32;
182 -       }
183 +                       n -= (git_statuses.show ? 34 : 32);
184 +       } else if (cfg.normalgit && git_statuses.show)
185 +               n -= 3;
186
187         /* 2 columns for preceding space and indicator */
188         return (n - 2);
189 @@ -8143,6 +8248,7 @@ static void usage(void)
190                 " -F val  fifo mode [0:preview 1:explore]\n"
191  #endif
192                 " -g      regex filters\n"
193 +               " -G      always show git status\n"
194                 " -H      show hidden files\n"
195                 " -i      show current file info\n"
196                 " -J      no auto-advance on selection\n"
197 @@ -8282,6 +8388,7 @@ static void cleanup(void)
198                 fflush(stdout);
199         }
200  #endif
201 +       free(git_statuses.statuses);
202         free(selpath);
203         free(plgpath);
204         free(cfgpath);
205 @@ -8326,7 +8433,7 @@ int main(int argc, char *argv[])
206
207         while ((opt = (env_opts_id > 0
208                        ? env_opts[--env_opts_id]
209 -                      : getopt(argc, argv, "aAb:BcCdDeEfF:gHiJKl:nNop:P:QrRs:St:T:uUVxh"))) != -1) {
210 +                      : getopt(argc, argv, "aAb:BcCdDeEfF:gGHiJKl:nNop:P:QrRs:St:T:uUVxh"))) != -1) {
211                 switch (opt) {
212  #ifndef NOFIFO
213                 case 'a':
214 @@ -8380,6 +8487,9 @@ int main(int argc, char *argv[])
215                         cfg.regex = 1;
216                         filterfn = &visible_re;
217                         break;
218 +               case 'G':
219 +                       cfg.normalgit = 1;
220 +                       break;
221                 case 'H':
222                         cfg.showhidden = 1;
223                         break;