* pass `QEmacsState` to key grab functions * clear `grab_key_opaque` when freeing windows, buffers, mode_data * add checks on window and buffer pointers in key grab functions * this should fix a crash bug when deleting unsynched buffers from bufed * fix `reload_buffer` to update buffer `file_size` and `mtime` fields * check buffer file before invoking commands that modify the buffer * check buffer file after command that changes the current window or the current window's buffer
2177 lines
67 KiB
C
2177 lines
67 KiB
C
/*
|
||
* Directory editor mode for QEmacs.
|
||
*
|
||
* Copyright (c) 2001-2002 Fabrice Bellard.
|
||
* Copyright (c) 2002-2026 Charlie Gordon.
|
||
*
|
||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||
* of this software and associated documentation files (the "Software"), to deal
|
||
* in the Software without restriction, including without limitation the rights
|
||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||
* copies of the Software, and to permit persons to whom the Software is
|
||
* furnished to do so, subject to the following conditions:
|
||
*
|
||
* The above copyright notice and this permission notice shall be included in
|
||
* all copies or substantial portions of the Software.
|
||
*
|
||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||
* THE SOFTWARE.
|
||
*/
|
||
|
||
#include "qe.h"
|
||
#include "variables.h"
|
||
|
||
#include <grp.h>
|
||
#include <pwd.h>
|
||
|
||
enum {
|
||
DIRED_STYLE_NORMAL = QE_STYLE_DEFAULT,
|
||
DIRED_STYLE_HEADER = QE_STYLE_STRING,
|
||
DIRED_STYLE_DIRECTORY = QE_STYLE_COMMENT,
|
||
DIRED_STYLE_FILENAME = QE_STYLE_FUNCTION,
|
||
};
|
||
|
||
enum {
|
||
DIRED_SORT_FULLNAME = 0,
|
||
DIRED_SORT_NAME = 1,
|
||
DIRED_SORT_EXTENSION = 2,
|
||
DIRED_SORT_SIZE = 4,
|
||
DIRED_SORT_DATE = 8,
|
||
DIRED_SORT_MASK = 1+2+4+8,
|
||
DIRED_SORT_GROUP = 16,
|
||
DIRED_SORT_DESCENDING = 32,
|
||
};
|
||
|
||
enum {
|
||
DIRED_UPDATE_SORT = 1,
|
||
DIRED_UPDATE_FILTER = 2,
|
||
DIRED_UPDATE_COLUMNS = 4,
|
||
DIRED_UPDATE_REBUILD = 8,
|
||
DIRED_UPDATE_ALL = 15,
|
||
};
|
||
|
||
enum time_format {
|
||
TF_COMPACT,
|
||
TF_DOS,
|
||
TF_DOS_LONG,
|
||
TF_TOUCH,
|
||
TF_TOUCH_LONG,
|
||
TF_FULL,
|
||
TF_SECONDS,
|
||
TF_MAX = TF_SECONDS,
|
||
};
|
||
|
||
typedef struct DiredState DiredState;
|
||
typedef struct DiredItem DiredItem;
|
||
|
||
struct DiredState {
|
||
QEModeData base; /* derived from QEModeData */
|
||
DiredItem **items;
|
||
int nb_items;
|
||
int nb_allocated;
|
||
enum time_format time_format;
|
||
int show_dot_files;
|
||
int show_ds_store;
|
||
int hflag, nflag, pf_flags;
|
||
int details_flag, last_details_flag;
|
||
int sort_mode;
|
||
DiredItem *last_cur;
|
||
long long total_bytes;
|
||
int ndirs, nfiles, ndirs_hidden, nfiles_hidden;
|
||
int blocksize;
|
||
int last_width;
|
||
int header_lines;
|
||
int details_mask;
|
||
#define DIRED_SHOW_BLOCKS 0x01
|
||
#define DIRED_SHOW_MODE 0x02
|
||
#define DIRED_SHOW_LINKS 0x04
|
||
#define DIRED_SHOW_UID 0x08
|
||
#define DIRED_SHOW_GID 0x10
|
||
#define DIRED_SHOW_SIZE 0x20
|
||
#define DIRED_SHOW_DATE 0x40
|
||
#define DIRED_SHOW_ALL 0x7F
|
||
int blockslen, modelen, linklen, uidlen, gidlen, sizelen, datelen, namelen;
|
||
int fnamecol;
|
||
char path[MAX_FILENAME_SIZE]; /* current path */
|
||
char target[MAX_FILENAME_SIZE]; /* current target */
|
||
char pattern[32]; /* filtering pattern */
|
||
};
|
||
|
||
/* opaque structure for sorting DiredState.items StringArray */
|
||
struct DiredItem {
|
||
char *name;
|
||
mode_t mode; /* inode protection mode */
|
||
nlink_t nlink; /* number of hard links to the file */
|
||
uid_t uid; /* user-id of owner */
|
||
gid_t gid; /* group-id of owner */
|
||
dev_t rdev; /* device type, for special file inode */
|
||
time_t mtime;
|
||
off_t size;
|
||
int offset;
|
||
u8 flags;
|
||
#define DI_ISLNK 1 /* XXX: use bitfields */
|
||
#define DI_BROKEN 2
|
||
#define DI_ISDIR 4
|
||
u8 level;
|
||
char hidden; /* XXX: use flag */
|
||
char mark;
|
||
char tick;
|
||
char fullname[1];
|
||
};
|
||
|
||
static ModeDef dired_mode;
|
||
|
||
static time_t dired_curtime;
|
||
|
||
static enum time_format dired_time_format;
|
||
static int dired_show_dot_files = 1;
|
||
static int dired_show_ds_store = 0;
|
||
static int dired_nflag = 0; /* 0=name, 1=numeric, 2=hidden */
|
||
static int dired_hflag = 0; /* 0=exact, 1=human-decimal, 2=human-binary */
|
||
#define DIRED_DETAILS_AUTO 0
|
||
#define DIRED_DETAILS_HIDE 1
|
||
#define DIRED_DETAILS_SHOW 2
|
||
static int dired_sort_mode = DIRED_SORT_GROUP | DIRED_SORT_FULLNAME;
|
||
static int dired_pf_flags;
|
||
// XXX: could use a regexp and make it extendable
|
||
static const char *dired_ignore_extensions = {
|
||
"|bak"
|
||
"|xls|xlsx|ppt|pptx"
|
||
"|apk"
|
||
"|bin|obj|dll|exe" /* DOS binaries */
|
||
"|o|so|a" /* Unix binaries */
|
||
"|dylib|dSYM" /* macOS */
|
||
"|cma|cmi|cmo|cmt|cmti|cmx"
|
||
"|class|jar" /* java */
|
||
"|b"
|
||
};
|
||
|
||
static QVarType dired_sort_mode_set_value(EditState *s, VarDef *vp,
|
||
void *ptr, const char *str, int sort_mode);
|
||
static QVarType dired_time_format_set_value(EditState *s, VarDef *vp,
|
||
void *ptr, const char *str, int format);
|
||
|
||
static VarDef dired_variables[] = {
|
||
G_VAR_F( "dired-sort-mode", dired_sort_mode, VAR_NUMBER, VAR_RW_SAVE,
|
||
dired_sort_mode_set_value,
|
||
"Sort order for dired display: any combination of `nefsdgur+-`" )
|
||
G_VAR_F( "dired-time-format", dired_time_format, VAR_NUMBER, VAR_RW_SAVE,
|
||
dired_time_format_set_value,
|
||
"Format used for file times (default, compact, dos, dos-long, touch, touch-long, full, seconds)" )
|
||
G_VAR( "dired-show-dot-files", dired_show_dot_files, VAR_NUMBER, VAR_RW_SAVE,
|
||
"Set to show hidden files (starting with a `.`)" )
|
||
G_VAR( "dired-show-ds-store", dired_show_ds_store, VAR_NUMBER, VAR_RW_SAVE,
|
||
"Set to show infamous macOS .DS_Store system files" )
|
||
};
|
||
|
||
static inline DiredState *dired_get_state(EditBuffer *b, EditState *s)
|
||
{
|
||
return qe_get_buffer_mode_data(b, &dired_mode, s);
|
||
}
|
||
|
||
static DiredItem *dired_get_cur_item(DiredState *ds, EditState *s) {
|
||
int i, index;
|
||
|
||
if (!s)
|
||
return NULL;
|
||
index = list_get_pos(s) - ds->header_lines;
|
||
if (index >= 0) {
|
||
for (i = 0; i < ds->nb_items; i++) {
|
||
DiredItem *dip = ds->items[i];
|
||
if (!dip->hidden && index-- == 0)
|
||
return dip;
|
||
}
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
static const char *dired_get_cur_filename(DiredState *ds, EditState *s,
|
||
char *buf, size_t buf_size)
|
||
{
|
||
DiredItem *dip = dired_get_cur_item(ds, s);
|
||
if (dip) {
|
||
pstrcpy(buf, buf_size, dip->fullname);
|
||
return buf;
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
static void dired_free(DiredState *ds)
|
||
{
|
||
if (ds) {
|
||
int i;
|
||
|
||
for (i = 0; i < ds->nb_items; i++) {
|
||
qe_free(&ds->items[i]);
|
||
}
|
||
|
||
qe_free(&ds->items);
|
||
ds->nb_items = 0;
|
||
ds->nb_allocated = 0;
|
||
ds->last_cur = NULL;
|
||
}
|
||
}
|
||
|
||
static char *dired_get_filename(DiredState *ds, const DiredItem *dip,
|
||
char *buf, int buf_size)
|
||
{
|
||
if (buf_size > 0)
|
||
buf[0] = '\0';
|
||
|
||
if (!dip)
|
||
return NULL;
|
||
|
||
pstrcpy(buf, buf_size, dip->fullname);
|
||
return buf;
|
||
}
|
||
|
||
static DiredItem *dired_goto_target(DiredState *ds, EditState *s, const char *target, int force)
|
||
{
|
||
int best_row = force ? ds->header_lines : -1;
|
||
DiredItem *best_dip = NULL;
|
||
|
||
if (target && *target) {
|
||
int i, pos;
|
||
int best_pos = 0;
|
||
int row = ds->header_lines;
|
||
for (i = 0; i < ds->nb_items; i++) {
|
||
DiredItem *dip = ds->items[i];
|
||
const char *fn = dip->fullname;
|
||
|
||
if (dip->hidden)
|
||
continue;
|
||
|
||
for (pos = 0;; pos++) {
|
||
if (fn[pos] == '\0') {
|
||
if ((target[pos] == '\0' || target[pos] == '/') && pos > best_pos) {
|
||
best_pos = pos;
|
||
best_row = row;
|
||
best_dip = dip;
|
||
}
|
||
break;
|
||
}
|
||
if (target[pos] != fn[pos])
|
||
break;
|
||
}
|
||
row++;
|
||
}
|
||
}
|
||
if (best_row >= 0 && s) {
|
||
s->offset = eb_goto_pos(s->b, best_row, 0);
|
||
}
|
||
return best_dip;
|
||
}
|
||
|
||
enum { SAME_NAME, DIR1_PARENT, DIR2_PARENT, DIR1_UNCLE, DIR2_UNCLE, SAME_DIR, DIFFERENT_DIR };
|
||
|
||
static int classify_fullnames(const char *p1, const char *p2) {
|
||
if (*p1 == '/' && *p2 == '/' && p1[1] != p2[1]) {
|
||
if (p1[1] == '\0')
|
||
return DIR1_PARENT;
|
||
if (p2[1] == '\0')
|
||
return DIR2_PARENT;
|
||
}
|
||
for (; *p1 == *p2; p1++, p2++) {
|
||
if (*p1 == '\0')
|
||
return SAME_NAME;
|
||
}
|
||
if (*p1 == '\0' && *p2 == '/')
|
||
return DIR1_PARENT;
|
||
if (*p2 == '\0' && *p1 == '/')
|
||
return DIR2_PARENT;
|
||
p1 = strchr(p1, '/');
|
||
p2 = strchr(p2, '/');
|
||
if (p1 && p2)
|
||
return DIFFERENT_DIR;
|
||
if (p1)
|
||
return DIR2_UNCLE;
|
||
if (p2)
|
||
return DIR1_UNCLE;
|
||
return SAME_DIR;
|
||
}
|
||
|
||
/* sort accoding to sort criteria */
|
||
static int dired_sort_func(void *opaque, const void *p1, const void *p2)
|
||
{
|
||
DiredState *ds = opaque;
|
||
const DiredItem * const *pp1 = (const DiredItem * const *)p1;
|
||
const DiredItem * const *pp2 = (const DiredItem * const *)p2;
|
||
const DiredItem *dip1 = *pp1;
|
||
const DiredItem *dip2 = *pp2;
|
||
int sort_mode = ds->sort_mode, res = 0;
|
||
int is_dir1 = dip1->flags & DI_ISDIR;
|
||
int is_dir2 = dip2->flags & DI_ISDIR;
|
||
|
||
if (sort_mode & DIRED_SORT_GROUP) {
|
||
/* when grouped, directories are always sorted in alpha order */
|
||
switch (classify_fullnames(dip1->fullname, dip2->fullname)) {
|
||
case SAME_NAME: /* same full name: should not happen */
|
||
case SAME_DIR: /* dip1 and dip2 are in the same directory */
|
||
/* sort directories before files in the same directory */
|
||
if (is_dir1 != is_dir2)
|
||
return is_dir2 - is_dir1;
|
||
if (is_dir1)
|
||
goto cmpnames;
|
||
break;
|
||
case DIR1_PARENT: /* dip1 is the parent directory of dip2 */
|
||
return -1;
|
||
case DIR1_UNCLE: /* dip1 is an entry in a parent directory of dip2 */
|
||
if (!is_dir1)
|
||
return 1;
|
||
goto cmpnames;
|
||
case DIR2_PARENT: /* dip2 is the parent directory of dip1 */
|
||
return 1;
|
||
case DIR2_UNCLE: /* dip2 is an entry in a parent directory of dip1 */
|
||
if (!is_dir2)
|
||
return -1;
|
||
goto cmpnames;
|
||
case DIFFERENT_DIR: /* different directories specified */
|
||
cmpnames:
|
||
return qe_strcollate(dip1->fullname, dip2->fullname);
|
||
}
|
||
}
|
||
|
||
if ((sort_mode & DIRED_SORT_DATE) && dip1->mtime != dip2->mtime) {
|
||
res = (dip1->mtime < dip2->mtime) ? -1 : 1;
|
||
} else
|
||
if ((sort_mode & DIRED_SORT_SIZE) && dip1->size != dip2->size) {
|
||
res = (dip1->size < dip2->size) ? -1 : 1;
|
||
} else {
|
||
if (sort_mode & DIRED_SORT_EXTENSION) {
|
||
res = qe_strcollate(get_extension(dip1->name),
|
||
get_extension(dip2->name));
|
||
}
|
||
if (!res && (sort_mode & DIRED_SORT_NAME)) {
|
||
res = qe_strcollate(dip1->name, dip2->name);
|
||
}
|
||
if (!res) {
|
||
res = qe_strcollate(dip1->fullname, dip2->fullname);
|
||
}
|
||
}
|
||
return (sort_mode & DIRED_SORT_DESCENDING) ? -res : res;
|
||
}
|
||
|
||
int eb_put_filename(EditBuffer *b, const char *name, int flags) {
|
||
// write a filename to a buffer, encoding special characters according
|
||
// to flags
|
||
// XXX: use local array to write to buffer as a single modification?
|
||
int len = 0;
|
||
char buf[8];
|
||
u8 cc;
|
||
while ((cc = *name++) != '\0') {
|
||
if (cc > 0x7F && !(flags & PF_NO_UNICODE)) {
|
||
const char *p = name - 1;
|
||
char32_t c = utf8_decode(&p);
|
||
if (c >= 128 && c <= 0x10FFFF) {
|
||
int nc = utf8_encode(buf, c);
|
||
if (nc == p - name + 1 && !memcmp(buf, name - 1, nc)) {
|
||
// valid UTF-8 encoded codepoint
|
||
if (flags & 1) {
|
||
if (c > 0xFFFF) {
|
||
len += eb_printf(b, "\\u{%X}", c);
|
||
} else {
|
||
len += eb_printf(b, "\\x%04X", c);
|
||
}
|
||
} else {
|
||
buf[nc] = '\0';
|
||
eb_puts(b, buf);
|
||
len += qe_wcwidth(c);
|
||
}
|
||
name = p;
|
||
continue;
|
||
}
|
||
}
|
||
}
|
||
if (cc < ' ' || cc >= 0x7F) {
|
||
switch (flags & PF_ENCODING) {
|
||
case PF_QUESTION:
|
||
eb_putc(b, '?');
|
||
len += 1;
|
||
break;
|
||
case PF_OCTAL:
|
||
eb_putc(b, '\\');
|
||
len += 2;
|
||
switch (cc) {
|
||
case '\n': eb_putc(b, 'n'); break;
|
||
case '\t': eb_putc(b, 't'); break;
|
||
case '\r': eb_putc(b, 'r'); break;
|
||
case '\b': eb_putc(b, 'b'); break;
|
||
case '\f': eb_putc(b, 'f'); break;
|
||
default: eb_printf(b, "%03o", cc); len += 2; break;
|
||
}
|
||
break;
|
||
case PF_HEX:
|
||
eb_printf(b, "\\x%02X", cc);
|
||
len += 4;
|
||
break;
|
||
case PF_CARET:
|
||
if (cc < 32 || cc == 127) {
|
||
eb_printf(b, "\\^%c", (cc + '@') & 127);
|
||
len += 3;
|
||
break;
|
||
}
|
||
eb_printf(b, "\\x%02X", cc);
|
||
len += 4;
|
||
break;
|
||
}
|
||
} else {
|
||
eb_putc(b, cc);
|
||
len += 1;
|
||
}
|
||
}
|
||
return len;
|
||
}
|
||
|
||
static int format_number(char *buf, int size, int human, off_t number)
|
||
{
|
||
if (human == 0) {
|
||
return snprintf(buf, size, "%lld", (long long)number);
|
||
}
|
||
if (human > 1) {
|
||
const char *suffix = "BkMGTPEZY";
|
||
|
||
/* metric version, powers of 1000 */
|
||
while (suffix[1] && number >= 1000) {
|
||
if (number < 10000) {
|
||
buf[0] = '0' + (number / 1000);
|
||
buf[1] = '.';
|
||
buf[2] = '0' + ((number / 100) % 10);
|
||
buf[3] = suffix[1];
|
||
buf[4] = '\0';
|
||
return 4;
|
||
}
|
||
number /= 1000;
|
||
suffix++;
|
||
}
|
||
return snprintf(buf, size, "%d%c", (int)number, *suffix);
|
||
} else {
|
||
const char *suffix = "BKMGTPEZY";
|
||
|
||
/* geek version, powers of 1024 */
|
||
while (suffix[1] && number >= 1000) {
|
||
if (number < 10200) {
|
||
buf[0] = '0' + (number / 1020);
|
||
buf[1] = '.';
|
||
buf[2] = '0' + ((number / 102) % 10);
|
||
buf[3] = suffix[1];
|
||
buf[4] = '\0';
|
||
return 4;
|
||
}
|
||
number >>= 10;
|
||
suffix++;
|
||
}
|
||
return snprintf(buf, size, "%d%c", (int)number, *suffix);
|
||
}
|
||
}
|
||
|
||
static int format_gid(char *buf, int size, int nflag, gid_t gid)
|
||
{
|
||
// group_from_gid ?
|
||
struct group *grp;
|
||
|
||
if (!nflag && (grp = getgrgid(gid)) != NULL && grp->gr_name)
|
||
return snprintf(buf, size, "%s", grp->gr_name);
|
||
else
|
||
return snprintf(buf, size, "%d", (int)gid);
|
||
}
|
||
|
||
static int format_uid(char *buf, int size, int nflag, uid_t uid)
|
||
{
|
||
// user_from_uid ?
|
||
struct passwd *pwp;
|
||
|
||
if (!nflag && (pwp = getpwuid(uid)) != NULL && pwp->pw_name)
|
||
return snprintf(buf, size, "%s", pwp->pw_name);
|
||
else
|
||
return snprintf(buf, size, "%d", (int)uid);
|
||
}
|
||
|
||
static int format_size(char *buf, int size, int human,
|
||
mode_t st_mode, dev_t st_rdev, off_t st_size)
|
||
{
|
||
if (S_ISCHR(st_mode) || S_ISBLK(st_mode)) {
|
||
int major = st_rdev >> ((sizeof(dev_t) == 2) ? 8 : 24);
|
||
int minor = st_rdev & ((sizeof(dev_t) == 2) ? 0xff : 0xffffff);
|
||
return snprintf(buf, size, "%3d, %3d", major, minor);
|
||
} else {
|
||
return format_number(buf, size, human, st_size);
|
||
}
|
||
}
|
||
|
||
static int format_date(char *dest, int size,
|
||
const time_t systime,
|
||
enum time_format time_format)
|
||
{
|
||
buf_t outbuf, *out;
|
||
struct tm systm;
|
||
int fmonth;
|
||
static const char * const month[] = {
|
||
"***",
|
||
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
||
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
|
||
};
|
||
|
||
/* Should test for valid conversion */
|
||
/* Should support extra precision on enabled systems */
|
||
systm = *localtime(&systime);
|
||
|
||
fmonth = systm.tm_mon + 1;
|
||
if (fmonth <= 0 || fmonth > 12)
|
||
fmonth = 0;
|
||
|
||
out = buf_init(&outbuf, dest, size);
|
||
|
||
switch (time_format) {
|
||
case TF_TOUCH:
|
||
case TF_TOUCH_LONG:
|
||
buf_printf(out, "%02d%02d%02d%02d%02d",
|
||
systm.tm_year % 100, /* year */
|
||
fmonth, /* month */
|
||
systm.tm_mday, /* day */
|
||
systm.tm_hour, /* hours */
|
||
systm.tm_min); /* minutes */
|
||
if (time_format == TF_TOUCH_LONG) {
|
||
buf_printf(out, ".%02d", systm.tm_sec); /* seconds */
|
||
}
|
||
break;
|
||
case TF_DOS:
|
||
case TF_DOS_LONG:
|
||
buf_printf(out, "%s %2d %4d %2d:%02d",
|
||
month[fmonth], /* month */
|
||
systm.tm_mday, /* day */
|
||
systm.tm_year + 1900, /* year */
|
||
systm.tm_hour, /* hours */
|
||
systm.tm_min); /* minutes */
|
||
if (time_format == TF_DOS_LONG) {
|
||
buf_printf(out, ":%02d", systm.tm_sec); /* seconds */
|
||
}
|
||
break;
|
||
case TF_FULL:
|
||
buf_printf(out, "%s %2d %02d:%02d:%02d %4d",
|
||
month[fmonth], /* month */
|
||
systm.tm_mday, /* day */
|
||
systm.tm_hour, /* hours */
|
||
systm.tm_min, /* minutes */
|
||
systm.tm_sec, /* seconds */
|
||
systm.tm_year + 1900); /* year */
|
||
break;
|
||
case TF_SECONDS:
|
||
buf_printf(out, "%10lu", (unsigned long)systime); /* seconds */
|
||
break;
|
||
default:
|
||
case TF_COMPACT:
|
||
if (systime > dired_curtime - 182 * 86400
|
||
&& systime < dired_curtime + 182 * 86400) {
|
||
buf_printf(out, "%s %2d %02d:%02d",
|
||
month[fmonth], /* month */
|
||
systm.tm_mday, /* day */
|
||
systm.tm_hour, /* hours */
|
||
systm.tm_min); /* minutes */
|
||
} else {
|
||
buf_printf(out, "%s %2d %4d",
|
||
month[fmonth], /* month */
|
||
systm.tm_mday, /* day */
|
||
systm.tm_year + 1900); /* year */
|
||
}
|
||
break;
|
||
}
|
||
|
||
if (!fmonth) {
|
||
memset(dest, ' ', out->len);
|
||
}
|
||
return out->pos;
|
||
}
|
||
|
||
static int get_trailchar(mode_t mode)
|
||
{
|
||
int trailchar = 0;
|
||
|
||
if (mode & S_IXUSR) {
|
||
trailchar = '*';
|
||
}
|
||
if (S_ISDIR(mode)) {
|
||
trailchar = '/';
|
||
}
|
||
if (S_ISLNK(mode)) {
|
||
trailchar = '@';
|
||
}
|
||
#ifdef S_ISSOCK
|
||
if (S_ISSOCK(mode))
|
||
trailchar = '=';
|
||
#endif
|
||
#ifdef S_ISWHT
|
||
if (S_ISWHT(mode))
|
||
trailchar = '%';
|
||
#endif
|
||
#ifdef S_ISFIFO
|
||
if (S_ISFIFO(mode))
|
||
trailchar = '|';
|
||
#endif
|
||
return trailchar;
|
||
}
|
||
|
||
static char *getentryslink(char *path, int size, const char *filename)
|
||
{
|
||
/* Warning: readlink does not append a null byte! */
|
||
int len = readlink(filename, path, size - 1);
|
||
if (len < 0)
|
||
len = 0;
|
||
path[len] = '\0';
|
||
if (len)
|
||
return path;
|
||
else
|
||
return NULL;
|
||
}
|
||
|
||
static char *compute_attr(char *atts, mode_t mode)
|
||
{
|
||
strcpy(atts, "----------");
|
||
|
||
/* File type */
|
||
if (!S_ISREG(mode)) {
|
||
if (S_ISDIR(mode)) /* directory */
|
||
atts[0] = 'd';
|
||
#ifdef S_ISBLK
|
||
if (S_ISBLK(mode)) /* block special */
|
||
atts[0] = 'b';
|
||
#endif
|
||
#ifdef S_ISCHR
|
||
if (S_ISCHR(mode)) /* char special */
|
||
atts[0] = 'c';
|
||
#endif
|
||
#ifdef S_ISFIFO
|
||
if (S_ISFIFO(mode)) /* fifo or socket */
|
||
atts[0] = 'p';
|
||
#endif
|
||
#ifdef S_ISSOCK
|
||
if (S_ISSOCK(mode) /* socket */)
|
||
atts[0] = 's';
|
||
#endif
|
||
if (S_ISLNK(mode)) /* symbolic link */
|
||
atts[0] = 'l'; /* overrides directory */
|
||
}
|
||
|
||
/* File mode */
|
||
/* Read, write, execute/search by owner */
|
||
if (mode & S_IRUSR) /* [XSI] R for owner */
|
||
atts[1] = 'r';
|
||
if (mode & S_IWUSR) /* [XSI] W for owner */
|
||
atts[2] = 'w';
|
||
if (mode & S_IXUSR) /* [XSI] X for owner */
|
||
atts[3] = 'x';
|
||
#ifdef S_ISUID
|
||
if (mode & S_ISUID) /* [XSI] set user id on execution */
|
||
atts[3] = (mode & S_IXUSR) ? 's' : 'S';
|
||
#endif
|
||
/* Read, write, execute/search by group */
|
||
if (mode & S_IRGRP) /* [XSI] R for group */
|
||
atts[4] = 'r';
|
||
if (mode & S_IWGRP) /* [XSI] W for group */
|
||
atts[5] = 'w';
|
||
if (mode & S_IXGRP) /* [XSI] X for group */
|
||
atts[6] = 'x';
|
||
#ifdef S_ISGID
|
||
if (mode & S_ISGID) /* [XSI] set group id on execution */
|
||
atts[6] = (mode & S_IXGRP) ? 's' : 'S';
|
||
#endif
|
||
/* Read, write, execute/search by others */
|
||
if (mode & S_IROTH)
|
||
atts[7] = 'r'; /* [XSI] R for other */
|
||
if (mode & S_IWOTH)
|
||
atts[8] = 'w'; /* [XSI] W for other */
|
||
if (mode & S_IXOTH)
|
||
atts[9] = 'x'; /* [XSI] X for other */
|
||
#ifdef S_ISVTX
|
||
if (mode & S_ISVTX) /* [XSI] directory restrcted delete */
|
||
atts[6] = (mode & S_IXOTH) ? 't' : 'T';
|
||
#endif
|
||
return atts;
|
||
}
|
||
|
||
static void dired_filter_files(DiredState *ds)
|
||
{
|
||
int i;
|
||
|
||
ds->show_dot_files = dired_show_dot_files;
|
||
ds->show_ds_store = dired_show_ds_store;
|
||
ds->total_bytes = 0;
|
||
ds->ndirs = ds->nfiles = 0;
|
||
ds->ndirs_hidden = ds->nfiles_hidden = 0;
|
||
|
||
for (i = 0; i < ds->nb_items; i++) {
|
||
DiredItem *dip = ds->items[i];
|
||
const char *p = dip->name;
|
||
int hidden = 0;
|
||
|
||
if (*p == '.') {
|
||
if ((!dired_show_dot_files)
|
||
|| (!dired_show_ds_store && strequal(p, ".DS_Store")))
|
||
hidden = 1;
|
||
} else
|
||
if (!dired_show_dot_files) {
|
||
const char *ext = get_extension(dip->fullname);
|
||
if (*ext && strfind(dired_ignore_extensions, ext + 1))
|
||
hidden = 1;
|
||
}
|
||
/* XXX: should apply other filters? */
|
||
// XXX: should hide full subtree if grouped?
|
||
dip->hidden = hidden;
|
||
if (hidden) {
|
||
if (S_ISDIR(dip->mode)) {
|
||
ds->ndirs_hidden++;
|
||
} else {
|
||
ds->nfiles_hidden++;
|
||
}
|
||
} else {
|
||
if (S_ISDIR(dip->mode)) {
|
||
ds->ndirs++;
|
||
} else {
|
||
ds->nfiles++;
|
||
ds->total_bytes += dip->size;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
static void dired_compute_columns(DiredState *ds)
|
||
{
|
||
char buf[32];
|
||
int i, len;
|
||
|
||
dired_curtime = time(NULL);
|
||
ds->time_format = dired_time_format;
|
||
ds->hflag = dired_hflag;
|
||
ds->nflag = dired_nflag;
|
||
ds->pf_flags = dired_pf_flags;
|
||
ds->blockslen = ds->modelen = ds->linklen = 0;
|
||
ds->uidlen = ds->gidlen = 0;
|
||
ds->sizelen = ds->datelen = ds->namelen = 0;
|
||
|
||
for (i = 0; i < ds->nb_items; i++) {
|
||
DiredItem *dip = ds->items[i];
|
||
|
||
len = strlen(dip->name);
|
||
if (ds->namelen < len)
|
||
ds->namelen = len;
|
||
|
||
if (ds->details_flag == DIRED_DETAILS_HIDE)
|
||
continue;
|
||
|
||
len = snprintf(buf, sizeof(buf), "%ld",
|
||
(long)(((long long)dip->size + ds->blocksize - 1) /
|
||
ds->blocksize));
|
||
if (ds->blockslen < len)
|
||
ds->blockslen = len;
|
||
|
||
ds->modelen = 10;
|
||
|
||
len = snprintf(buf, sizeof(buf), "%d", (int)dip->nlink);
|
||
if (ds->linklen < len)
|
||
ds->linklen = len;
|
||
|
||
len = format_uid(buf, sizeof(buf), ds->nflag, dip->uid);
|
||
if (ds->uidlen < len)
|
||
ds->uidlen = len;
|
||
|
||
len = format_gid(buf, sizeof(buf), ds->nflag, dip->gid);
|
||
if (ds->gidlen < len)
|
||
ds->gidlen = len;
|
||
|
||
len = format_size(buf, sizeof(buf), ds->hflag, dip->mode, dip->rdev, dip->size);
|
||
if (ds->sizelen < len)
|
||
ds->sizelen = len;
|
||
|
||
len = format_date(buf, sizeof(buf), dip->mtime, dired_time_format);
|
||
if (ds->datelen < len)
|
||
ds->datelen = len;
|
||
}
|
||
}
|
||
|
||
static int dired_format_details(DiredState *ds, DiredItem *dip,
|
||
int details_mask, char *dest, size_t size)
|
||
{
|
||
char buf[32];
|
||
buf_t bp[1];
|
||
|
||
buf_init(bp, dest, size);
|
||
|
||
#if 0
|
||
if (details_mask & DIRED_SHOW_BLOCKS) {
|
||
buf_printf(bp, "%*ld ", ds->blockslen,
|
||
(long)(((long long)dip->size + ds->blocksize - 1) / ds->blocksize));
|
||
}
|
||
#endif
|
||
if (details_mask & DIRED_SHOW_MODE) {
|
||
buf_printf(bp, "%s ", compute_attr(buf, dip->mode));
|
||
}
|
||
if (details_mask & DIRED_SHOW_LINKS) {
|
||
buf_printf(bp, "%*d ", ds->linklen, (int)dip->nlink);
|
||
}
|
||
if (details_mask & DIRED_SHOW_UID) {
|
||
format_uid(buf, sizeof(buf), ds->nflag, dip->uid);
|
||
buf_printf(bp, "%-*s ", ds->uidlen, buf);
|
||
}
|
||
if (details_mask & DIRED_SHOW_GID) {
|
||
format_gid(buf, sizeof(buf), ds->nflag, dip->gid);
|
||
buf_printf(bp, "%-*s ", ds->gidlen, buf);
|
||
}
|
||
if (details_mask & DIRED_SHOW_SIZE) {
|
||
format_size(buf, sizeof(buf), ds->hflag, dip->mode, dip->rdev, dip->size);
|
||
buf_printf(bp, " %*s ", ds->sizelen, buf);
|
||
}
|
||
if (details_mask & DIRED_SHOW_DATE) {
|
||
format_date(buf, sizeof(buf), dip->mtime, dired_time_format);
|
||
buf_printf(bp, " %s ", buf);
|
||
}
|
||
return bp->len;
|
||
}
|
||
|
||
#define inflect(n, singular, plural) ((n) == 1 ? (singular) : (plural))
|
||
|
||
/* `b` is valid, `ds` and `s` may be NULL */
|
||
static void dired_update_buffer(DiredState *ds, EditBuffer *b, EditState *s,
|
||
int flags)
|
||
{
|
||
QEmacsState *qs = b->qs;
|
||
char buf[MAX_FILENAME_SIZE];
|
||
DiredItem *dip, *cur_item;
|
||
int i, col, w, width, window_width, top_line, indent;
|
||
int header_lines;
|
||
const char *fname;
|
||
|
||
if (!ds)
|
||
return;
|
||
|
||
header_lines = ds->header_lines;
|
||
/* Try and preserve scroll position */
|
||
if (s) {
|
||
w = max_int(1, get_glyph_width(s->screen, s, QE_STYLE_DEFAULT, '0'));
|
||
window_width = s->width;
|
||
width = window_width / w;
|
||
|
||
eb_get_pos(s->b, &top_line, &col, s->offset_top);
|
||
/* XXX: should use dip->offset and delay to rebuild phase */
|
||
cur_item = dired_get_cur_item(ds, s);
|
||
header_lines = 2;
|
||
if (s->width <= qs->width / 3)
|
||
header_lines = 1;
|
||
} else {
|
||
width = window_width = 80;
|
||
col = top_line = 0;
|
||
cur_item = NULL;
|
||
}
|
||
|
||
if (ds->header_lines != header_lines) {
|
||
ds->header_lines = header_lines;
|
||
flags |= DIRED_UPDATE_REBUILD;
|
||
}
|
||
|
||
if (ds->sort_mode != dired_sort_mode)
|
||
flags |= DIRED_UPDATE_SORT;
|
||
|
||
if (flags & DIRED_UPDATE_SORT) {
|
||
flags |= DIRED_UPDATE_REBUILD;
|
||
ds->sort_mode = dired_sort_mode;
|
||
qe_qsort_r(ds->items, ds->nb_items, sizeof(DiredItem *),
|
||
ds, dired_sort_func);
|
||
}
|
||
|
||
if (ds->show_dot_files != dired_show_dot_files
|
||
|| ds->show_ds_store != dired_show_ds_store) {
|
||
flags |= DIRED_UPDATE_FILTER;
|
||
}
|
||
|
||
if (flags & DIRED_UPDATE_FILTER) {
|
||
flags |= DIRED_UPDATE_REBUILD;
|
||
dired_filter_files(ds);
|
||
}
|
||
|
||
if (ds->time_format != dired_time_format
|
||
|| ds->nflag != dired_nflag
|
||
|| ds->hflag != dired_hflag
|
||
|| ds->pf_flags != dired_pf_flags
|
||
|| ds->details_flag != ds->last_details_flag) {
|
||
flags |= DIRED_UPDATE_COLUMNS;
|
||
}
|
||
|
||
if (flags & DIRED_UPDATE_COLUMNS) {
|
||
flags |= DIRED_UPDATE_REBUILD;
|
||
dired_compute_columns(ds);
|
||
}
|
||
|
||
if (!(flags & DIRED_UPDATE_REBUILD))
|
||
return;
|
||
|
||
ds->last_details_flag = ds->details_flag;
|
||
ds->last_width = window_width;
|
||
ds->last_cur = NULL;
|
||
width -= clamp_int(ds->namelen, 16, 40);
|
||
ds->details_mask = DIRED_SHOW_ALL;
|
||
if (ds->header_lines == 1 || ds->details_flag == DIRED_DETAILS_HIDE) {
|
||
ds->details_mask = 0;
|
||
} else
|
||
if (ds->details_flag == DIRED_DETAILS_AUTO) {
|
||
if ((width -= ds->sizelen + 2) < 0)
|
||
ds->details_mask ^= DIRED_SHOW_SIZE;
|
||
if ((width -= ds->datelen + 2) < 0)
|
||
ds->details_mask ^= DIRED_SHOW_DATE;
|
||
if ((width -= ds->modelen + 1) < 0)
|
||
ds->details_mask ^= DIRED_SHOW_MODE;
|
||
if ((ds->nflag == 2) || ((width -= ds->uidlen + 1) < 0))
|
||
ds->details_mask ^= DIRED_SHOW_UID;
|
||
if ((ds->nflag == 2) || ((width -= ds->gidlen + 1) < 0))
|
||
ds->details_mask ^= DIRED_SHOW_GID;
|
||
if ((width -= ds->linklen + 1) < 0)
|
||
ds->details_mask ^= DIRED_SHOW_LINKS;
|
||
// disable blocks display to avoid confusing output
|
||
ds->details_mask ^= DIRED_SHOW_BLOCKS;
|
||
}
|
||
|
||
/* construct list buffer */
|
||
/* deleting buffer contents resets s->offset and s->offset_top */
|
||
eb_clear(b);
|
||
|
||
if (ds->header_lines == 1) {
|
||
b->cur_style = DIRED_STYLE_HEADER;
|
||
eb_puts(b, " Explorer \n");
|
||
} else {
|
||
int seq = ' ';
|
||
b->cur_style = DIRED_STYLE_HEADER;
|
||
eb_puts(b, " Directory of ");
|
||
b->cur_style = DIRED_STYLE_DIRECTORY;
|
||
eb_put_filename(b, ds->path, ds->pf_flags);
|
||
b->cur_style = DIRED_STYLE_HEADER;
|
||
eb_puts(b, "\n ");
|
||
if (ds->ndirs) {
|
||
eb_printf(b, "%c %d %s", seq, ds->ndirs,
|
||
inflect(ds->ndirs, "directory", "directories"));
|
||
seq = ',';
|
||
}
|
||
if (ds->ndirs_hidden) {
|
||
eb_printf(b, "%c %d %s", seq, ds->ndirs_hidden,
|
||
inflect(ds->ndirs_hidden, "hidden directory", "hidden directories"));
|
||
seq = ',';
|
||
}
|
||
if (ds->nfiles) {
|
||
eb_printf(b, "%c %d %s", seq, ds->nfiles,
|
||
inflect(ds->nfiles, "file", "files"));
|
||
seq = ',';
|
||
}
|
||
if (ds->nfiles_hidden) {
|
||
eb_printf(b, "%c %d %s", seq, ds->nfiles_hidden,
|
||
inflect(ds->nfiles_hidden, "hidden file", "hidden files"));
|
||
seq = ',';
|
||
}
|
||
if (ds->total_bytes) {
|
||
format_number(buf, sizeof(buf), ds->hflag, ds->total_bytes);
|
||
eb_printf(b, "%c %s %s", seq, buf,
|
||
inflect(ds->total_bytes, "byte", "bytes"));
|
||
seq = ',';
|
||
}
|
||
if (ds->ndirs + ds->ndirs_hidden + ds->nfiles + ds->nfiles_hidden == 0) {
|
||
eb_printf(b, "%c empty", seq);
|
||
}
|
||
eb_putc(b, '\n');
|
||
}
|
||
b->cur_style = DIRED_STYLE_NORMAL;
|
||
|
||
for (i = 0; i < ds->nb_items; i++) {
|
||
dip = ds->items[i];
|
||
dip->offset = b->offset;
|
||
if (dip == cur_item) {
|
||
ds->last_cur = dip;
|
||
if (s)
|
||
s->offset = b->offset;
|
||
}
|
||
if (dip->hidden)
|
||
continue;
|
||
|
||
dired_format_details(ds, dip, ds->details_mask, buf, sizeof buf);
|
||
col = eb_printf(b, "%c %s", dip->mark, buf);
|
||
ds->fnamecol = col;
|
||
if (ds->sort_mode & DIRED_SORT_GROUP) {
|
||
fname = dip->name;
|
||
indent = dip->level;
|
||
} else {
|
||
if (*dip->name == '~' || *dip->name == '/') {
|
||
fname = dip->name;
|
||
} else {
|
||
fname = get_relativename(dip->fullname, ds->path);
|
||
fname += (*fname == '/' && fname[1] != '\0');
|
||
}
|
||
indent = 0;
|
||
}
|
||
col += eb_printf(b, "%*s%c ", indent, "", dip->tick);
|
||
|
||
if (dip->flags & DI_ISDIR)
|
||
b->cur_style = DIRED_STYLE_DIRECTORY;
|
||
else
|
||
b->cur_style = DIRED_STYLE_FILENAME;
|
||
|
||
eb_put_filename(b, fname, ds->pf_flags);
|
||
|
||
if (*fname != '/' || fname[1]) {
|
||
int trailchar = get_trailchar(dip->mode);
|
||
if (trailchar) {
|
||
eb_putc(b, trailchar);
|
||
}
|
||
}
|
||
if (S_ISLNK(dip->mode)
|
||
&& getentryslink(buf, sizeof(buf), dip->fullname)) {
|
||
eb_puts(b, " -> ");
|
||
eb_put_filename(b, buf, ds->pf_flags);
|
||
}
|
||
b->cur_style = DIRED_STYLE_NORMAL;
|
||
eb_putc(b, '\n');
|
||
}
|
||
b->modified = 0;
|
||
b->flags |= BF_READONLY;
|
||
if (s) {
|
||
s->offset_top = eb_goto_pos(b, top_line, 0);
|
||
}
|
||
}
|
||
|
||
/* dired-mode commands */
|
||
|
||
static void dired_up_down(EditState *s, int dir)
|
||
{
|
||
DiredState *ds;
|
||
int line;
|
||
|
||
if (!(ds = dired_get_state(s->b, s)))
|
||
return;
|
||
|
||
if (dir)
|
||
text_move_up_down(s, dir);
|
||
|
||
if (s->offset && s->offset == s->b->total_size)
|
||
text_move_up_down(s, -1);
|
||
|
||
line = list_get_pos(s);
|
||
if (line >= ds->header_lines)
|
||
s->offset = eb_goto_pos(s->b, line, 0);
|
||
}
|
||
|
||
static void dired_mark(EditState *s, int mark)
|
||
{
|
||
DiredState *ds;
|
||
DiredItem *dip;
|
||
int dir = 1, flags;
|
||
char32_t ch;
|
||
|
||
if (!(ds = dired_get_state(s->b, s)))
|
||
return;
|
||
|
||
if (mark < 0) {
|
||
dir = -1;
|
||
mark = ' ';
|
||
}
|
||
if (dir < 0)
|
||
dired_up_down(s, -1);
|
||
|
||
dip = dired_get_cur_item(ds, s);
|
||
if (dip) {
|
||
ch = dip->mark = mark;
|
||
do_bol(s);
|
||
flags = s->b->flags & BF_READONLY;
|
||
s->b->flags ^= flags;
|
||
eb_replace_char32(s->b, s->offset, ch);
|
||
s->b->flags ^= flags;
|
||
}
|
||
if (dir > 0)
|
||
dired_up_down(s, 1);
|
||
}
|
||
|
||
static void sortkey_complete(CompleteState *cp, CompleteFunc enumerate) {
|
||
char current[MAX_FILENAME_SIZE];
|
||
const char *p;
|
||
size_t len;
|
||
|
||
pstrcpy(current, sizeof current, cp->current);
|
||
len = strlen(current);
|
||
if (len + 1 < sizeof(current)) {
|
||
current[len + 1] = '\0';
|
||
for (p = "fnesdug+-r"; *p; p++) {
|
||
current[len] = *p;
|
||
(*enumerate)(cp, current, CT_GLOB);
|
||
}
|
||
}
|
||
}
|
||
|
||
static int sortkey_print_entry(CompleteState *cp, EditState *s, const char *name) {
|
||
if (name && *name) {
|
||
char c = name[strlen(name) - 1];
|
||
const char *msg = "";
|
||
switch (c) {
|
||
case 'n': /* name */
|
||
msg = "sort entries by filename";
|
||
break;
|
||
case 'f': /* fullname */
|
||
msg = "sort entries by full pathname";
|
||
break;
|
||
case 'e': /* extension */
|
||
msg = "sort entries by file name extension";
|
||
break;
|
||
case 's': /* size */
|
||
msg = "sort entries by file size";
|
||
break;
|
||
case 'd': /* date */
|
||
msg = "sort entries by file modification time";
|
||
break;
|
||
case 'g': /* group */
|
||
msg = "group directories";
|
||
break;
|
||
case 'u': /* ungroup */
|
||
msg = "ungroup directories";
|
||
break;
|
||
case 'r': /* reverse */
|
||
msg = "reverse sorting order";
|
||
break;
|
||
case '+': /* ascending */
|
||
msg = "sort by ascending order";
|
||
break;
|
||
case '-': /* descending */
|
||
msg = "sort by descending";
|
||
break;
|
||
}
|
||
return eb_printf(s->b, "%c\t%s", c, msg);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static CompletionDef dired_sort_completion = {
|
||
.name = "sortkey",
|
||
.enumerate = sortkey_complete,
|
||
.print_entry = sortkey_print_entry,
|
||
};
|
||
|
||
static QVarType dired_sort_mode_set_value(EditState *s, VarDef *vp,
|
||
void *ptr, const char *str, int sort_mode)
|
||
{
|
||
const char *p;
|
||
|
||
for (p = str; p && *p; p++) {
|
||
switch (qe_tolower((unsigned char)*p)) {
|
||
case 'n': /* name */
|
||
sort_mode &= ~DIRED_SORT_MASK;
|
||
sort_mode |= DIRED_SORT_NAME;
|
||
break;
|
||
case 'f': /* fullname */
|
||
sort_mode &= ~DIRED_SORT_MASK;
|
||
sort_mode |= DIRED_SORT_FULLNAME;
|
||
break;
|
||
case 'e': /* extension */
|
||
sort_mode &= ~DIRED_SORT_MASK;
|
||
sort_mode |= DIRED_SORT_EXTENSION;
|
||
break;
|
||
case 's': /* size */
|
||
sort_mode &= ~DIRED_SORT_MASK;
|
||
sort_mode |= DIRED_SORT_SIZE;
|
||
break;
|
||
case 'd': /* date */
|
||
sort_mode &= ~DIRED_SORT_MASK;
|
||
sort_mode |= DIRED_SORT_DATE;
|
||
break;
|
||
case 'g': /* group */
|
||
sort_mode |= DIRED_SORT_GROUP;
|
||
break;
|
||
case 'u': /* ungroup */
|
||
sort_mode &= ~DIRED_SORT_GROUP;
|
||
break;
|
||
case 'r': /* reverse */
|
||
sort_mode ^= DIRED_SORT_DESCENDING;
|
||
break;
|
||
case '+': /* ascending */
|
||
sort_mode &= ~DIRED_SORT_DESCENDING;
|
||
break;
|
||
case '-': /* descending */
|
||
sort_mode |= DIRED_SORT_DESCENDING;
|
||
break;
|
||
}
|
||
}
|
||
if (dired_sort_mode != sort_mode) {
|
||
/* XXX: should broadcast modification for side
|
||
* effect on all windows.
|
||
*/
|
||
dired_sort_mode = sort_mode;
|
||
vp->modified = 1;
|
||
}
|
||
return VAR_NUMBER;
|
||
}
|
||
|
||
static void dired_sort(EditState *s, const char *sort_order)
|
||
{
|
||
DiredState *ds = dired_get_state(s->b, NULL);
|
||
|
||
dired_sort_mode_set_value(s, &dired_variables[0], &dired_sort_mode,
|
||
sort_order, dired_sort_mode);
|
||
|
||
// FIXME: should update all dired buffers
|
||
if (ds)
|
||
dired_update_buffer(ds, s->b, s, 0);
|
||
}
|
||
|
||
static QVarType dired_time_format_set_value(EditState *s, VarDef *vp,
|
||
void *ptr, const char *str, int format)
|
||
{
|
||
if (str) {
|
||
if (!strxcmp(str, "default")) format = TF_COMPACT; else
|
||
if (!strxcmp(str, "compact")) format = TF_COMPACT; else
|
||
if (!strxcmp(str, "dos")) format = TF_DOS; else
|
||
if (!strxcmp(str, "dos-long")) format = TF_DOS_LONG; else
|
||
if (!strxcmp(str, "touch")) format = TF_TOUCH; else
|
||
if (!strxcmp(str, "touch-long")) format = TF_TOUCH_LONG; else
|
||
if (!strxcmp(str, "full")) format = TF_FULL; else
|
||
if (!strxcmp(str, "seconds")) format = TF_SECONDS; else
|
||
return VAR_UNKNOWN;
|
||
}
|
||
if (format < 0 || format > TF_MAX)
|
||
return VAR_UNKNOWN;
|
||
|
||
if (dired_time_format != (enum time_format)format) {
|
||
dired_time_format = format;
|
||
vp->modified = 1;
|
||
}
|
||
return VAR_NUMBER;
|
||
}
|
||
|
||
static void dired_set_time_format(EditState *s, int format)
|
||
{
|
||
dired_time_format_set_value(s, &dired_variables[1], &dired_time_format,
|
||
NULL, format);
|
||
}
|
||
|
||
static DiredItem *dired_add_item(DiredState *ds, const char *name,
|
||
const char *fullname, int level)
|
||
{
|
||
struct stat st;
|
||
DiredItem *dip;
|
||
size_t fullname_size = strlen(fullname) + 1;
|
||
size_t name_size = strlen(name) + 1;
|
||
size_t name_offset = fullname_size;
|
||
|
||
if (lstat(fullname, &st) < 0)
|
||
memset(&st, 0, sizeof st);
|
||
|
||
if (fullname_size >= name_size
|
||
&& !memcmp(fullname + fullname_size - name_size, name, name_size)) {
|
||
/* share space for name and fullname */
|
||
name_offset = fullname_size - name_size;
|
||
name_size = 0;
|
||
}
|
||
|
||
dip = qe_malloc_hack(DiredItem, fullname_size + name_size);
|
||
if (!dip)
|
||
return NULL;
|
||
dip->flags = 0;
|
||
if (S_ISLNK(st.st_mode)) {
|
||
struct stat st1;
|
||
dip->flags |= DI_ISLNK;
|
||
if (!stat(fullname, &st1)) {
|
||
if (S_ISDIR(st1.st_mode))
|
||
dip->flags |= DI_ISDIR;
|
||
} else {
|
||
/* broken symbolic link */
|
||
dip->flags |= DI_BROKEN;
|
||
}
|
||
} else
|
||
if (S_ISDIR(st.st_mode)) {
|
||
dip->flags |= DI_ISDIR;
|
||
}
|
||
dip->mode = st.st_mode;
|
||
dip->nlink = st.st_nlink;
|
||
dip->uid = st.st_uid;
|
||
dip->gid = st.st_gid;
|
||
dip->rdev = st.st_rdev;
|
||
dip->mtime = st.st_mtime;
|
||
dip->size = st.st_size;
|
||
dip->hidden = 0;
|
||
dip->mark = ' ';
|
||
dip->tick = ' ';
|
||
dip->level = level;
|
||
if (dip->flags & DI_ISDIR)
|
||
dip->tick = '>';
|
||
memcpy(dip->fullname, fullname, fullname_size);
|
||
dip->name = dip->fullname + name_offset;
|
||
memcpy(dip->name, name, name_size);
|
||
|
||
if (ds->nb_items >= ds->nb_allocated) {
|
||
int n = ds->nb_allocated + (ds->nb_allocated / 2) + 32;
|
||
if (!qe_realloc_array(&ds->items, n)) {
|
||
qe_free(&dip);
|
||
return NULL;
|
||
}
|
||
ds->nb_allocated = n;
|
||
}
|
||
return ds->items[ds->nb_items++] = dip;
|
||
}
|
||
|
||
/* `ds` and `dir` are valid, `dip` and `pattern` may be NULL */
|
||
static int dired_expand_dir(DiredState *ds, DiredItem *dip,
|
||
const char *dir, const char *pattern)
|
||
{
|
||
FindFileState *ffst;
|
||
char filename[MAX_FILENAME_SIZE];
|
||
int count = 0;
|
||
int level = dip ? dip->level + 1 : 0;
|
||
|
||
/* XXX: should scan directory for subdirectories and filter with
|
||
* pattern only for regular files.
|
||
* XXX: should handle generalized file patterns.
|
||
* XXX: should use a separate thread to make the scan asynchronous.
|
||
* XXX: should compute recursive size data.
|
||
* XXX: should track file creation, deletion and modifications.
|
||
*/
|
||
if (!pattern)
|
||
pattern = "*";
|
||
ffst = find_file_open(dir, pattern, FF_NOXXDIR);
|
||
while (!find_file_next(ffst, filename, sizeof(filename))) {
|
||
if (dired_add_item(ds, get_basename(filename), filename, level))
|
||
count++;
|
||
}
|
||
find_file_close(&ffst);
|
||
if (dip)
|
||
dip->tick = count ? 'v' : '-';
|
||
return count;
|
||
}
|
||
|
||
static int dired_collapse_dir(DiredState *ds, DiredItem *dip0)
|
||
{
|
||
int i, j, count = 0;
|
||
size_t len = strlen(dip0->fullname);
|
||
|
||
if (dip0->flags & DI_ISDIR)
|
||
dip0->tick = '>';
|
||
// XXX: should hide the whole subtree?
|
||
for (i = j = 0; i < ds->nb_items; i++) {
|
||
DiredItem *dip = ds->items[i];
|
||
if (dip != dip0
|
||
&& !strncmp(dip0->fullname, dip->fullname, len)
|
||
&& dip->fullname[len] == '/')
|
||
{
|
||
qe_free(&ds->items[i]);
|
||
count++;
|
||
} else {
|
||
ds->items[j++] = ds->items[i];
|
||
}
|
||
}
|
||
ds->nb_items = j;
|
||
return count;
|
||
}
|
||
|
||
/* `ds` and `b` are valid, `s` may be NULL */
|
||
static void dired_build_list(DiredState *ds, const char *path)
|
||
{
|
||
char dirname[MAX_FILENAME_SIZE];
|
||
char name[MAX_FILENAME_SIZE];
|
||
DiredItem *dip;
|
||
|
||
/* free previous list, if any */
|
||
dired_free(ds);
|
||
|
||
ds->last_cur = NULL;
|
||
ds->blocksize = 1024; /* XXX: should get from the filesystem */
|
||
ds->last_width = 0;
|
||
|
||
canonicalize_path(ds->path, sizeof(ds->path), path);
|
||
|
||
if (is_directory(ds->path)) {
|
||
pstrcpy(dirname, sizeof dirname, ds->path);
|
||
strcpy(ds->pattern, "*");
|
||
} else {
|
||
get_dirname(dirname, sizeof dirname, ds->path);
|
||
pstrcpy(ds->pattern, sizeof(ds->pattern), get_basename(ds->path));
|
||
if (!is_filepattern(ds->pattern))
|
||
strcpy(ds->pattern, "*");
|
||
}
|
||
|
||
if (ds->header_lines == 1) {
|
||
make_user_path(name, sizeof name, dirname);
|
||
dip = dired_add_item(ds, name, dirname, 0);
|
||
dired_expand_dir(ds, dip, dirname, ds->pattern);
|
||
} else {
|
||
dired_expand_dir(ds, NULL, dirname, ds->pattern);
|
||
}
|
||
}
|
||
|
||
/* select current item */
|
||
static void dired_select(EditState *s, int mode)
|
||
{
|
||
char filename[MAX_FILENAME_SIZE];
|
||
DiredState *ds;
|
||
struct stat st;
|
||
EditState *e;
|
||
DiredItem *dip;
|
||
|
||
if (!(ds = dired_get_state(s->b, s)))
|
||
return;
|
||
|
||
dip = dired_get_cur_item(ds, s);
|
||
if (!dip) {
|
||
dired_goto_target(ds, s, ds->target, TRUE);
|
||
return;
|
||
}
|
||
|
||
if (dip->flags & DI_ISDIR) {
|
||
if (dip->tick == '>' || dip->tick == '-') {
|
||
dired_expand_dir(ds, dip, dip->fullname, NULL);
|
||
dired_update_buffer(ds, s->b, s, DIRED_UPDATE_ALL);
|
||
if (classify_fullnames(dip->fullname, ds->target) == DIR1_PARENT)
|
||
dired_goto_target(ds, s, ds->target, TRUE);
|
||
} else
|
||
if (dip->tick == 'v') {
|
||
if (mode == 2) {
|
||
dired_collapse_dir(ds, dip);
|
||
dired_update_buffer(ds, s->b, s, DIRED_UPDATE_ALL);
|
||
} else {
|
||
if (classify_fullnames(dip->fullname, ds->target) != DIR1_PARENT
|
||
|| !dired_goto_target(ds, s, ds->target, TRUE)) {
|
||
dired_up_down(s, 1);
|
||
}
|
||
}
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (!dired_get_filename(ds, dip, filename, sizeof(filename)))
|
||
return;
|
||
|
||
/* Check if path leads somewhere */
|
||
if (stat(filename, &st) < 0)
|
||
return;
|
||
|
||
if (S_ISDIR(st.st_mode)) {
|
||
/* DO descend into directories pointed to by symlinks */
|
||
/* XXX: should expand directory below current position
|
||
* or merge generated items with existing items in ds->items */
|
||
dired_build_list(ds, filename);
|
||
dired_update_buffer(ds, s->b, s, DIRED_UPDATE_ALL);
|
||
} else
|
||
if (S_ISREG(st.st_mode)) {
|
||
/* do explore files pointed to by symlinks */
|
||
e = find_window(s, KEY_RIGHT, NULL);
|
||
if (e) {
|
||
#if 1
|
||
s->qs->active_window = e;
|
||
if (mode == 1) {
|
||
/* XXX: should keep BF_PREVIEW flag and set pager-mode */
|
||
e->b->flags &= ~BF_PREVIEW;
|
||
}
|
||
#else
|
||
/* delete dired window */
|
||
do_delete_window(s, 1);
|
||
/* XXX: should keep BF_PREVIEW flag and set pager-mode */
|
||
e->b->flags &= ~BF_PREVIEW;
|
||
#endif
|
||
} else {
|
||
do_find_file(s, filename, 0);
|
||
}
|
||
}
|
||
}
|
||
|
||
static EditState *dired_view_file(EditState *s, const char *filename)
|
||
{
|
||
EditBuffer *b;
|
||
EditState *e;
|
||
int rc;
|
||
|
||
e = find_window(s, KEY_RIGHT, NULL);
|
||
if (!e)
|
||
return NULL;
|
||
|
||
/* close previous temporary buffers, if any */
|
||
b = e->b;
|
||
if (b && (b->flags & BF_PREVIEW) && !b->modified) {
|
||
/* will free the buffer if no longer viewed */
|
||
b->flags |= BF_TRANSIENT;
|
||
}
|
||
|
||
/* Load file and attach to window. If file not loaded already, mark
|
||
* new buffer as BF_PREVIEW, to trigger paging mode and so that it
|
||
* will get freed if closed.
|
||
*/
|
||
rc = qe_load_file(e, filename, LF_NOWILDCARD, BF_PREVIEW);
|
||
if (rc >= 0) {
|
||
return e;
|
||
} else {
|
||
/* if file failed to load, show a scratch buffer */
|
||
b = qe_new_buffer(s->qs, "*scratch*", BF_SAVELOG | BF_UTF8 | BF_PREVIEW);
|
||
if (b) {
|
||
// XXX: should show error cause
|
||
eb_printf(b, "Cannot load file %s", filename);
|
||
switch_to_buffer(e, b);
|
||
}
|
||
return NULL;
|
||
}
|
||
}
|
||
|
||
static void dired_execute(EditState *s)
|
||
{
|
||
/* Actually delete, copy, or move the marked items */
|
||
put_error(s, "Not yet implemented");
|
||
}
|
||
|
||
static void dired_parent(EditState *s, int collapse)
|
||
{
|
||
char path[MAX_FILENAME_SIZE];
|
||
char dir[MAX_FILENAME_SIZE];
|
||
DiredItem *dip;
|
||
DiredState *ds;
|
||
|
||
if (!(ds = dired_get_state(s->b, s)))
|
||
return;
|
||
|
||
dip = dired_get_cur_item(ds, s);
|
||
if (dip) {
|
||
if (strcmp(dip->fullname, ds->path)) {
|
||
if (dip->tick == 'v' && collapse) {
|
||
dired_collapse_dir(ds, dip);
|
||
dired_update_buffer(ds, s->b, s, DIRED_UPDATE_ALL);
|
||
return;
|
||
}
|
||
get_dirname(dir, sizeof dir, dip->fullname);
|
||
if (dired_goto_target(ds, s, dir, FALSE))
|
||
return;
|
||
}
|
||
}
|
||
if (s->b->flags & BF_PREVIEW) {
|
||
EditState *e = find_window(s, KEY_LEFT, NULL);
|
||
if (e && (e->flags & WF_FILELIST)) {
|
||
s->qs->active_window = e;
|
||
return;
|
||
}
|
||
}
|
||
/* FIXME: should just prepend parent directory */
|
||
pstrcpy(path, sizeof path, ds->path);
|
||
get_dirname(dir, sizeof dir, path);
|
||
dired_build_list(ds, dir);
|
||
dired_update_buffer(ds, s->b, s, DIRED_UPDATE_ALL);
|
||
dip = dired_goto_target(ds, s, path, TRUE);
|
||
if (dip) {
|
||
dired_expand_dir(ds, dip, dip->fullname, NULL);
|
||
dired_update_buffer(ds, s->b, s, DIRED_UPDATE_ALL);
|
||
}
|
||
}
|
||
|
||
static void dired_toggle_human(EditState *s)
|
||
{
|
||
dired_hflag = (dired_hflag + 1) % 3;
|
||
}
|
||
|
||
static void dired_toggle_nflag(EditState *s)
|
||
{
|
||
dired_nflag = (dired_nflag + 1) % 3;
|
||
}
|
||
|
||
static void dired_cycle_encoding(EditState *s)
|
||
{
|
||
int encoding = dired_pf_flags & PF_ENCODING;
|
||
dired_pf_flags &= ~PF_ENCODING;
|
||
dired_pf_flags |= (encoding + 1) & PF_ENCODING;
|
||
}
|
||
|
||
static void dired_toggle_unicode(EditState *s)
|
||
{
|
||
dired_pf_flags ^= PF_NO_UNICODE;
|
||
}
|
||
|
||
static void dired_hide_details_mode(EditState *s)
|
||
{
|
||
DiredState *ds;
|
||
if (!(ds = dired_get_state(s->b, s)))
|
||
return;
|
||
|
||
ds->details_flag = (ds->details_flag + 1) % 3;
|
||
}
|
||
|
||
static void dired_refresh(EditState *s)
|
||
{
|
||
DiredState *ds;
|
||
char target[MAX_FILENAME_SIZE];
|
||
|
||
if (!(ds = dired_get_state(s->b, s)))
|
||
return;
|
||
|
||
*target = '\0';
|
||
dired_get_cur_filename(ds, s, target, sizeof(target));
|
||
dired_build_list(ds, ds->path);
|
||
dired_update_buffer(ds, s->b, s, DIRED_UPDATE_ALL);
|
||
dired_goto_target(ds, s, target, TRUE);
|
||
}
|
||
|
||
static void dired_toggle_dot_files(EditState *s, int val)
|
||
{
|
||
if (val == -1)
|
||
val = !dired_show_dot_files;
|
||
|
||
if (dired_show_dot_files != val) {
|
||
DiredState *ds = dired_get_state(s->b, NULL);
|
||
dired_show_dot_files = val;
|
||
if (ds)
|
||
dired_update_buffer(ds, s->b, s, DIRED_UPDATE_FILTER);
|
||
put_status(s, "Dot files are %s", val ? "visible" : "hidden");
|
||
}
|
||
}
|
||
|
||
static void dired_display_hook(EditState *s)
|
||
{
|
||
char filename[MAX_FILENAME_SIZE];
|
||
DiredState *ds;
|
||
DiredItem *dip;
|
||
int flags = 0;
|
||
|
||
if (!(ds = dired_get_state(s->b, NULL)))
|
||
return;
|
||
|
||
/* Prevent point from going beyond list */
|
||
if (s->offset && s->offset == s->b->total_size)
|
||
dired_up_down(s, -1);
|
||
|
||
if (s->x1 == 0 && s->y1 == 0 && ds->last_width != s->width) {
|
||
/* rebuild buffer contents according to new window width */
|
||
/* XXX: this may cause problems if buffer is displayed in
|
||
* multiple windows, hence the test on s->y1.
|
||
* Should test for current window */
|
||
flags |= DIRED_UPDATE_REBUILD;
|
||
}
|
||
|
||
dired_update_buffer(ds, s->b, s, flags);
|
||
|
||
if (s->x1 == 0) {
|
||
/* open file so that user can see it before it is selected */
|
||
/* XXX: find a better solution (callback) */
|
||
dip = dired_get_cur_item(ds, s);
|
||
if (dip && dip != ds->last_cur) {
|
||
ds->last_cur = dip;
|
||
if (dired_get_filename(ds, dip, filename, sizeof(filename))) {
|
||
dired_view_file(s, filename);
|
||
if (!(dip->flags & DI_ISDIR)) {
|
||
char tmp[MAX_FILENAME_SIZE];
|
||
char buf[64];
|
||
pstrcpy(ds->target, sizeof ds->target, filename);
|
||
dired_format_details(ds, dip, DIRED_SHOW_ALL, buf, sizeof buf);
|
||
put_status(s, "-> %s %s", buf, make_user_path(tmp, sizeof tmp, filename));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
static char *dired_get_default_path(EditBuffer *b, int offset,
|
||
char *buf, int buf_size)
|
||
{
|
||
DiredState *ds;
|
||
|
||
ds = dired_get_state(b, NULL);
|
||
if (ds) {
|
||
EditState *s = eb_find_window(b, NULL);
|
||
DiredItem *dip = dired_get_cur_item(ds, s);
|
||
if (dip && strcmp(dip->fullname, ds->path)) {
|
||
get_dirname(buf, buf_size, dip->fullname);
|
||
} else {
|
||
pstrcpy(buf, buf_size, ds->path);
|
||
if (!is_directory(buf))
|
||
get_dirname(buf, buf_size, buf);
|
||
}
|
||
append_slash(buf, buf_size);
|
||
return buf;
|
||
}
|
||
if (b->filename[0]) {
|
||
if (is_directory(b->filename)) {
|
||
pstrcpy(buf, buf_size, b->filename);
|
||
} else {
|
||
get_dirname(buf, buf_size, b->filename);
|
||
}
|
||
append_slash(buf, buf_size);
|
||
return buf;
|
||
} else {
|
||
return getcwd(buf, buf_size) ? buf : NULL;
|
||
}
|
||
}
|
||
|
||
static int dired_mode_init(EditState *s, EditBuffer *b, int flags)
|
||
{
|
||
DiredState *ds = qe_get_buffer_mode_data(b, &dired_mode, NULL);
|
||
|
||
if (!ds)
|
||
return -1;
|
||
|
||
list_mode.mode_init(s, b, flags);
|
||
|
||
if (flags & MODEF_NEWINSTANCE) {
|
||
b->flags |= BF_DIRED;
|
||
ds->header_lines = 2;
|
||
if (s->width <= s->qs->width / 3)
|
||
ds->header_lines = 1;
|
||
eb_create_style_buffer(b, BF_STYLE1);
|
||
/* XXX: File system charset should be detected automatically */
|
||
/* XXX: If file system charset is not utf8, eb_printf will fail */
|
||
eb_set_charset(b, &charset_utf8, b->eol_type);
|
||
/* XXX: should be built by buffer_load API */
|
||
if (*b->filename) {
|
||
dired_build_list(ds, b->filename);
|
||
dired_update_buffer(ds, b, s, DIRED_UPDATE_ALL);
|
||
s->offset = eb_goto_pos(b, ds->header_lines, 0);
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static void dired_mode_free(EditBuffer *b, void *state)
|
||
{
|
||
DiredState *ds = state;
|
||
|
||
dired_free(ds);
|
||
}
|
||
|
||
/* can only apply dired mode on directories and file patterns */
|
||
static int dired_mode_probe(ModeDef *mode, ModeProbeData *p)
|
||
{
|
||
if (qe_get_buffer_mode_data(p->b, &dired_mode, NULL))
|
||
return 100;
|
||
|
||
if (S_ISDIR(p->st_mode))
|
||
return 95;
|
||
|
||
if (p->st_errno == ENOENT && is_filepattern(p->real_filename))
|
||
return 90;
|
||
|
||
return 0;
|
||
}
|
||
|
||
void do_dired_path(EditState *s, const char *filename)
|
||
{
|
||
QEmacsState *qs = s->qs;
|
||
EditBuffer *b;
|
||
EditState *e;
|
||
DiredState *ds;
|
||
|
||
if ((s->flags & WF_POPLEFT) && (s->b->flags & BF_DIRED)
|
||
&& (ds = dired_get_state(s->b, NULL)) != NULL) {
|
||
/* rebuild from current entry */
|
||
dired_get_cur_filename(ds, s, ds->target, sizeof ds->target);
|
||
e = s;
|
||
b = s->b;
|
||
goto has_buffer;
|
||
}
|
||
for (e = qs->first_window; e != NULL; e = e->next_window) {
|
||
if ((e->flags & WF_POPLEFT) && (e->b->flags & BF_DIRED)
|
||
&& (ds = dired_get_state(e->b, NULL)) != NULL) {
|
||
/* reuse existing dired pane */
|
||
b = e->b;
|
||
goto new_window;
|
||
}
|
||
}
|
||
b = qe_new_buffer(qs, "*dired*", BC_CLEAR | BF_READONLY | BF_UTF8);
|
||
if (!b)
|
||
return;
|
||
|
||
e = insert_window_left(b, qs->width / 5, WF_MODELINE | WF_FILELIST);
|
||
if (!e)
|
||
return;
|
||
/* set dired mode: dired_mode_init() will load buffer content */
|
||
edit_set_mode(e, &dired_mode);
|
||
ds = dired_get_state(b, NULL);
|
||
if (!ds)
|
||
return;
|
||
|
||
new_window:
|
||
/* modify active window */
|
||
qs->active_window = e;
|
||
/* Set target as specified filename (or directory) */
|
||
pstrcpy(ds->target, sizeof ds->target, filename);
|
||
|
||
has_buffer:
|
||
dired_build_list(ds, filename);
|
||
dired_update_buffer(ds, b, e, DIRED_UPDATE_ALL);
|
||
dired_goto_target(ds, e, ds->target, TRUE);
|
||
}
|
||
|
||
/* open dired window on the left. The directory of the current file is used */
|
||
void do_dired(EditState *s, int argval)
|
||
{
|
||
char filename[MAX_FILENAME_SIZE];
|
||
|
||
if (argval != NO_ARG) {
|
||
do_filelist(s, argval);
|
||
return;
|
||
}
|
||
|
||
pstrcpy(filename, sizeof filename, s->b->filename);
|
||
if (!*filename) {
|
||
get_default_path(s->b, s->offset, filename, sizeof filename);
|
||
}
|
||
do_dired_path(s, filename);
|
||
}
|
||
|
||
/* specific dired commands */
|
||
static const CmdDef dired_commands[] = {
|
||
/* Emacs bindings:
|
||
e .. f dired-find-file
|
||
! dired-do-shell-command
|
||
$ dired-hide-subdir
|
||
+ dired-create-directory
|
||
- negative-argument
|
||
0 .. 9 digit-argument
|
||
< dired-prev-dirline
|
||
= dired-diff
|
||
> dired-next-dirline
|
||
? dired-summary
|
||
A dired-do-search
|
||
B dired-do-byte-compile
|
||
C dired-do-copy
|
||
D dired-do-delete
|
||
G dired-do-chgrp
|
||
H dired-do-hardlink
|
||
L dired-do-load
|
||
M dired-do-chmod
|
||
O dired-do-chown
|
||
P dired-do-print
|
||
Q dired-do-query-replace-regexp
|
||
R dired-do-rename
|
||
rename a file or move selection to another directory
|
||
S dired-do-symlink
|
||
T dired-do-touch
|
||
U dired-unmark-all-marks
|
||
X dired-do-shell-command
|
||
Z dired-do-compress
|
||
^ dired-up-directory
|
||
a dired-find-alternate-file
|
||
h describe-mode
|
||
i, + dired-maybe-insert-subdir
|
||
j dired-goto-file
|
||
g revert-buffer
|
||
read all currently expanded directories aGain.
|
||
k dired-do-kill-lines
|
||
l dired-do-redisplay
|
||
relist single directory or marked files?
|
||
o dired-find-file-other-window
|
||
q quit-window
|
||
s dired-sort-toggle-or-edit
|
||
toggle sorting by name and by date
|
||
with prefix: set the ls command line options
|
||
t dired-toggle-marks
|
||
v dired-view-file
|
||
w dired-copy-filename-as-kill
|
||
x dired-do-flagged-delete
|
||
y dired-show-file-type
|
||
~ dired-flag-backup-files
|
||
M-C-d dired-tree-down
|
||
M-C-n dired-next-subdir
|
||
M-C-p dired-prev-subdir
|
||
M-C-u dired-tree-up
|
||
M-$ dired-hide-all
|
||
M-{ dired-prev-marked-file
|
||
M-} dired-next-marked-file
|
||
M-DEL dired-unmark-all-files
|
||
* C-n dired-next-marked-file
|
||
* C-p dired-prev-marked-file
|
||
* ! dired-unmark-all-marks
|
||
* % dired-mark-files-regexp
|
||
* * dired-mark-executables
|
||
* / dired-mark-directories
|
||
* ? dired-unmark-all-files
|
||
* @ dired-mark-symlinks
|
||
* c dired-change-marks
|
||
* m dired-mark
|
||
* s dired-mark-subdir-files
|
||
* t dired-toggle-marks
|
||
* u dired-unmark
|
||
* need commands for splitting, unsplitting, zooming,
|
||
marking files globally.
|
||
*/
|
||
CMD1( "dired-enter", "RET, LF",
|
||
"Select the current entry",
|
||
dired_select, 1)
|
||
CMD1( "dired-expand", "SPC",
|
||
"Expand / collapse directory",
|
||
dired_select, 2)
|
||
CMD1( "dired-right", "right",
|
||
"Select the current entry in preview mode",
|
||
dired_select, 0)
|
||
CMD0( "dired-tab", "TAB",
|
||
"Move focus to the current file",
|
||
do_other_window)
|
||
/* dired-abort should restore previous buffer in right-window */
|
||
CMD1( "dired-abort", "C-g, q",
|
||
"Quit the dired mode",
|
||
do_delete_window, 0)
|
||
CMD1( "dired-unmark-backward", "DEL",
|
||
"Move to the previous entry and unmark it",
|
||
dired_mark, -1)
|
||
CMD2( "dired-sort", "s",
|
||
"Sort entries using option string",
|
||
dired_sort, ESs,
|
||
"s{Sort order [fnesdug+-r]: }[sortkey]|sortkey|")
|
||
CMD2( "dired-set-time-format", "t",
|
||
"Select the format for file times",
|
||
dired_set_time_format, ESi,
|
||
"n{Time format: }[timeformat]")
|
||
CMD1( "dired-delete", "d",
|
||
"Mark the entry for deletion",
|
||
dired_mark, 'D')
|
||
CMD1( "dired-copy", "c",
|
||
"Mark the entry for copying",
|
||
dired_mark, 'C')
|
||
CMD1( "dired-mark", "m",
|
||
"Mark the entry for something",
|
||
dired_mark, '*')
|
||
CMD1( "dired-unmark", "u",
|
||
"Unmark the current entry",
|
||
dired_mark, ' ')
|
||
CMD0( "dired-execute", "x",
|
||
"Execute the pending operations on marked entries (not implemented yet)",
|
||
dired_execute)
|
||
CMD1( "dired-next-line", "n, C-n, down",
|
||
"Move to the next entry",
|
||
dired_up_down, 1)
|
||
CMD1( "dired-previous-line", "p, C-p, up",
|
||
"Move to the previous entry",
|
||
dired_up_down, -1)
|
||
CMD0( "dired-refresh", "r",
|
||
"Refresh directory contents",
|
||
dired_refresh)
|
||
CMD1( "dired-toggle-dot-files", ".",
|
||
"Display or hide entries starting with .",
|
||
dired_toggle_dot_files, -1)
|
||
CMD1( "dired-parent", "^",
|
||
"Select the parent directory",
|
||
dired_parent, 0)
|
||
CMD1( "dired-collapse-or-parent", "left",
|
||
"Collapse the directory or select the parent directory",
|
||
dired_parent, 1)
|
||
CMD0( "dired-toggle-human", "H",
|
||
"Change the format for file sizes (human readable vs: actual byte count)",
|
||
dired_toggle_human)
|
||
CMD0( "dired-toggle-nflag", "N",
|
||
"Change the format for uid and gid (name vs: number)",
|
||
dired_toggle_nflag)
|
||
CMD0( "dired-hide-details-mode", "(",
|
||
"Toggle visibility of detailed information in current Dired buffer)",
|
||
dired_hide_details_mode)
|
||
CMD0( "dired-cycle-encoding", "\\",
|
||
"Cycle display of non printing characters in filenames",
|
||
dired_cycle_encoding)
|
||
CMD0( "dired-toggle-unicode", "U",
|
||
"Toggle display of Unicode characters in filenames",
|
||
dired_toggle_unicode)
|
||
CMD2( "dired-summary", "?",
|
||
"Display a summary of dired commands",
|
||
do_apropos, ESs, "@{dired}")
|
||
};
|
||
|
||
static const CmdDef dired_global_commands[] = {
|
||
CMD2( "dired", "C-x C-d",
|
||
"Display the directory window and start dired mode",
|
||
do_dired, ESi, "#" "P")
|
||
};
|
||
|
||
#if 0
|
||
static int dired_buffer_load(EditBuffer *b, FILE *f)
|
||
{
|
||
/* XXX: launch subprocess to list dired contents */
|
||
return -1;
|
||
}
|
||
|
||
static int dired_buffer_save(EditBuffer *b, int start, int end,
|
||
const char *filename)
|
||
{
|
||
/* XXX: prevent saving parsed contents to dired file */
|
||
return -1;
|
||
}
|
||
|
||
static void dired_buffer_close(EditBuffer *b)
|
||
{
|
||
/* XXX: free dired structures? */
|
||
}
|
||
|
||
static EditBufferDataType dired_data_type = {
|
||
"dired",
|
||
dired_buffer_load,
|
||
dired_buffer_save,
|
||
dired_buffer_close,
|
||
NULL, /* next */
|
||
};
|
||
#endif
|
||
|
||
static int filelist_init(QEmacsState *qs);
|
||
|
||
static int dired_init(QEmacsState *qs)
|
||
{
|
||
/* inherit from list mode */
|
||
/* CG: assuming list_mode already initialized ? */
|
||
// XXX: remove this mess
|
||
memcpy(&dired_mode, &list_mode, offsetof(ModeDef, first_key));
|
||
dired_mode.name = "dired";
|
||
dired_mode.mode_probe = dired_mode_probe;
|
||
dired_mode.buffer_instance_size = sizeof(DiredState);
|
||
dired_mode.mode_init = dired_mode_init;
|
||
dired_mode.mode_free = dired_mode_free;
|
||
/* CG: not a good idea, display hook has side effect on layout */
|
||
dired_mode.display_hook = dired_display_hook;
|
||
dired_mode.get_default_path = dired_get_default_path;
|
||
|
||
//qe_register_data_type(qs, &dired_data_type);
|
||
qe_register_mode(qs, &dired_mode, /* MODEF_DATATYPE | */ MODEF_MAJOR | MODEF_VIEW);
|
||
qe_register_variables(qs, dired_variables, countof(dired_variables));
|
||
qe_register_commands(qs, &dired_mode, dired_commands, countof(dired_commands));
|
||
qe_register_commands(qs, NULL, dired_global_commands, countof(dired_global_commands));
|
||
qe_register_completion(qs, &dired_sort_completion);
|
||
|
||
filelist_init(qs);
|
||
|
||
return 0;
|
||
}
|
||
|
||
int file_print_entry(CompleteState *cp, EditState *s, const char *name) {
|
||
struct stat st;
|
||
EditBuffer *b = s->b;
|
||
char buf[20];
|
||
int len, sizelen = 10, linklen = 2, uidlen = 8, gidlen = 8;
|
||
|
||
if (!stat(name, &st)) {
|
||
b->cur_style = S_ISDIR(st.st_mode) ? DIRED_STYLE_DIRECTORY : DIRED_STYLE_FILENAME;
|
||
len = eb_put_filename(b, name, dired_pf_flags);
|
||
b->tab_width = max3_int(16, len + 2, b->tab_width);
|
||
b->cur_style = DIRED_STYLE_NORMAL;
|
||
format_size(buf, sizeof(buf), dired_hflag, st.st_mode, st.st_dev, st.st_size);
|
||
len += eb_printf(b, "\t%*s", sizelen, buf);
|
||
format_date(buf, sizeof(buf), st.st_mtime, dired_time_format);
|
||
len += eb_printf(b, " %s", buf);
|
||
len += eb_printf(b, " %s", compute_attr(buf, st.st_mode));
|
||
format_uid(buf, sizeof(buf), dired_nflag, st.st_uid);
|
||
len += eb_printf(b, " %-*s", uidlen, buf);
|
||
format_gid(buf, sizeof(buf), dired_nflag, st.st_gid);
|
||
len += eb_printf(b, " %-*s", gidlen, buf);
|
||
len += eb_printf(b, " %*d", linklen, (int)st.st_nlink);
|
||
} else {
|
||
len = eb_put_filename(b, name, dired_pf_flags);
|
||
}
|
||
return len;
|
||
}
|
||
|
||
/*---------------- filelist mode ----------------*/
|
||
|
||
static char filelist_last_buf[MAX_FILENAME_SIZE];
|
||
|
||
static ModeDef filelist_mode;
|
||
|
||
static void filelist_display_hook(EditState *s)
|
||
{
|
||
char buf[MAX_FILENAME_SIZE];
|
||
char dir[MAX_FILENAME_SIZE];
|
||
char filename[MAX_FILENAME_SIZE];
|
||
QEmacsState *qs = s->qs;
|
||
EditState *e;
|
||
int i, len, offset, target_line;
|
||
|
||
offset = eb_goto_bol(s->b, s->offset);
|
||
len = eb_fgets(s->b, buf, sizeof(buf), offset, &offset);
|
||
buf[len] = '\0'; /* strip the trailing newline if any */
|
||
|
||
if (s->x1 == 0 && s->y1 == 0 && s->width != qs->width
|
||
&& *buf && !strequal(buf, filelist_last_buf)) {
|
||
/* open file so that user can see it before it is selected */
|
||
/* XXX: find a better solution (callback) */
|
||
pstrcpy(filelist_last_buf, sizeof(filelist_last_buf), buf);
|
||
get_default_path(s->b, offset, dir, sizeof(dir));
|
||
makepath(filename, sizeof(filename), dir, buf);
|
||
target_line = 0;
|
||
if (access(filename, R_OK)) {
|
||
/* try parsing an error message: `:` or `(` a linenumber */
|
||
i = strcspn(buf, ":(");
|
||
if (i < len) {
|
||
char c = buf[i];
|
||
buf[i] = '\0';
|
||
makepath(filename, sizeof(filename), dir, buf);
|
||
buf[i] = c;
|
||
target_line = strtol(buf + i + 1, NULL, 10);
|
||
}
|
||
i = 0;
|
||
while (access(filename, R_OK)) {
|
||
/* try skipping initial words */
|
||
i += strcspn(buf + i, " ");
|
||
i += strspn(buf + i, " ");
|
||
if (i == len)
|
||
break;
|
||
makepath(filename, sizeof(filename), dir, buf + i);
|
||
}
|
||
}
|
||
if (!access(filename, R_OK)) {
|
||
e = dired_view_file(s, filename);
|
||
if (e) {
|
||
if (target_line > 0)
|
||
do_goto_line(e, target_line, 0);
|
||
}
|
||
put_status(e, "Previewing %s", filename);
|
||
} else {
|
||
put_error(s, "No access to %s", filename);
|
||
}
|
||
}
|
||
}
|
||
|
||
void do_filelist(EditState *s, int argval)
|
||
{
|
||
QEmacsState *qs = s->qs;
|
||
EditState *e;
|
||
|
||
e = insert_window_left(s->b, qs->width / 5, WF_MODELINE | WF_FILELIST);
|
||
if (e != NULL) {
|
||
edit_set_mode(e, &filelist_mode);
|
||
/* XXX: should come from mode.default_wrap */
|
||
e->wrap = WRAP_TRUNCATE;
|
||
filelist_last_buf[0] = '\0';
|
||
qs->active_window = e;
|
||
}
|
||
}
|
||
|
||
static int filelist_mode_init(EditState *s, EditBuffer *b, int flags)
|
||
{
|
||
if (s) {
|
||
/* XXX: should come from mode.default_wrap */
|
||
s->wrap = WRAP_TRUNCATE;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static const CmdDef filelist_commands[] = {
|
||
CMD2( "filelist-select", "RET, LF, right",
|
||
"Select the current entry",
|
||
do_other_window, ES, "#")
|
||
CMD2( "filelist-tab", "TAB",
|
||
"Select the current entry",
|
||
do_other_window, ES, "#")
|
||
/* filelist-abort should restore previous buffer in right-window
|
||
* or at least exit preview mode */
|
||
CMD1( "filelist-abort", "C-g",
|
||
"Quit the filelist mode",
|
||
do_delete_window, 0)
|
||
};
|
||
|
||
static const CmdDef filelist_global_commands[] = {
|
||
CMD2( "filelist", "",
|
||
"Run the filelist-mode on the current region",
|
||
do_filelist, ESi, "#" "p")
|
||
};
|
||
|
||
static int filelist_init(QEmacsState *qs)
|
||
{
|
||
// XXX: remove this mess
|
||
memcpy(&filelist_mode, &text_mode, offsetof(ModeDef, first_key));
|
||
filelist_mode.name = "filelist";
|
||
filelist_mode.mode_probe = NULL;
|
||
filelist_mode.mode_init = filelist_mode_init;
|
||
filelist_mode.display_hook = filelist_display_hook;
|
||
|
||
qe_register_mode(qs, &filelist_mode, MODEF_VIEW);
|
||
qe_register_commands(qs, &filelist_mode, filelist_commands, countof(filelist_commands));
|
||
qe_register_commands(qs, NULL, filelist_global_commands, countof(filelist_global_commands));
|
||
return 0;
|
||
}
|
||
|
||
qe_module_init(dired_init);
|