/* * Directory editor mode for QEmacs. * * Copyright (c) 2001-2002 Fabrice Bellard. * Copyright (c) 2002-2014 Charlie Gordon. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "qe.h" enum { DIRED_HEADER = 0 }; enum { 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, }; typedef struct DiredState { StringArray items; int sort_mode; /* DIRED_SORT_GROUP | DIRED_SORT_NAME */ int last_index; char path[MAX_FILENAME_SIZE]; /* current path */ } DiredState; /* opaque structure for sorting DiredState.items StringArray */ typedef struct DiredItem { DiredState *state; mode_t st_mode; off_t size; time_t mtime; int offset; char mark; char name[1]; } DiredItem; static inline int dired_get_index(EditState *s) { return list_get_pos(s) - DIRED_HEADER; } static void dired_free(EditState *s) { DiredState *ds = s->mode_data; int i; /* free opaques */ for (i = 0; i < ds->items.nb_items; i++) { qe_free(&ds->items.items[i]->opaque); } free_strings(&ds->items); ds->last_index = -1; /* reset cursor position */ s->offset_top = 0; s->offset = 0; } static char *dired_get_filename(EditState *s, char *buf, int buf_size, int index) { DiredState *hs = s->mode_data; const StringItem *item; const DiredItem *dip; /* CG: assuming buf_size > 0 */ buf[0] = '\0'; if (index < 0) index = dired_get_index(s); if (index < 0 || index >= hs->items.nb_items) return NULL; item = hs->items.items[index]; dip = item->opaque; /* build filename */ /* CG: Should canonicalize path */ return makepath(buf, buf_size, hs->path, dip->name); } static int dired_find_target(EditState *s, const char *target) { DiredState *hs = s->mode_data; char filename[MAX_FILENAME_SIZE]; int i; if (target) { for (i = 0; i < hs->items.nb_items; i++) { if (dired_get_filename(s, filename, sizeof(filename), i) && strequal(filename, target)) { return i; } } } return -1; } /* sort alphabetically with directories first */ static int dired_sort_func(const void *p1, const void *p2) { const StringItem *item1 = *(const StringItem **)p1; const StringItem *item2 = *(const StringItem **)p2; const DiredItem *dip1 = item1->opaque; const DiredItem *dip2 = item2->opaque; int sort_mode = dip1->state->sort_mode, res; int is_dir1, is_dir2; if (sort_mode & DIRED_SORT_GROUP) { is_dir1 = !!S_ISDIR(dip1->st_mode); is_dir2 = !!S_ISDIR(dip2->st_mode); if (is_dir1 != is_dir2) return is_dir2 - is_dir1; } for (;;) { if (sort_mode & DIRED_SORT_DATE) { if (dip1->mtime != dip2->mtime) { res = (dip1->mtime < dip2->mtime) ? -1 : 1; break; } } if (sort_mode & DIRED_SORT_SIZE) { if (dip1->size != dip2->size) { res = (dip1->size < dip2->size) ? -1 : 1; break; } } if (sort_mode & DIRED_SORT_EXTENSION) { res = qe_strcollate(get_extension(dip1->name), get_extension(dip2->name)); if (res) break; } res = qe_strcollate(dip1->name, dip2->name); break; } return (sort_mode & DIRED_SORT_DESCENDING) ? -res : res; } /* select current item */ static void dired_sort_list(EditState *s) { DiredState *hs = s->mode_data; StringItem *item, *cur_item; DiredItem *dip; EditBuffer *b; int index, i; index = dired_get_index(s); cur_item = NULL; if (index >= 0 && index < hs->items.nb_items) cur_item = hs->items.items[index]; qsort(hs->items.items, hs->items.nb_items, sizeof(StringItem *), dired_sort_func); /* construct list buffer */ b = s->b; b->flags &= ~BF_READONLY; eb_delete(b, 0, b->total_size); s->offset_top = 0; s->offset = 0; if (DIRED_HEADER) eb_printf(b, " %s:\n", hs->path); for (i = 0; i < hs->items.nb_items; i++) { item = hs->items.items[i]; dip = item->opaque; dip->offset = b->total_size; if (item == cur_item) { hs->last_index = i; s->offset = b->total_size; } eb_printf(b, "%c %s\n", dip->mark, item->str); } b->modified = 0; b->flags |= BF_READONLY; } static void dired_mark(EditState *s, int mark) { DiredState *hs = s->mode_data; const StringItem *item; DiredItem *dip; unsigned char ch; int index; index = dired_get_index(s); if (index < 0 || index >= hs->items.nb_items) return; item = hs->items.items[index]; dip = item->opaque; ch = dip->mark = mark; do_bol(s); s->b->flags &= ~BF_READONLY; eb_write(s->b, s->offset, &ch, 1); s->b->flags |= BF_READONLY; text_move_up_down(s, 1); } static void dired_sort(EditState *s, const char *sort_order) { DiredState *hs = s->mode_data; const char *p; for (p = sort_order; *p; p++) { switch (qe_tolower((unsigned char)*p)) { case 'n': /* name */ hs->sort_mode &= ~DIRED_SORT_MASK; hs->sort_mode |= DIRED_SORT_NAME; break; case 'e': /* extension */ hs->sort_mode &= ~DIRED_SORT_MASK; hs->sort_mode |= DIRED_SORT_EXTENSION; break; case 's': /* size */ hs->sort_mode &= ~DIRED_SORT_MASK; hs->sort_mode |= DIRED_SORT_SIZE; break; case 'd': /* direct */ hs->sort_mode &= ~DIRED_SORT_MASK; hs->sort_mode |= DIRED_SORT_DATE; break; case 'u': /* ungroup */ hs->sort_mode &= ~DIRED_SORT_GROUP; break; case 'g': /* group */ hs->sort_mode |= DIRED_SORT_GROUP; break; case '+': /* ascending */ hs->sort_mode &= ~DIRED_SORT_DESCENDING; break; case '-': /* descending */ hs->sort_mode |= DIRED_SORT_DESCENDING; break; case 'r': /* reverse */ hs->sort_mode ^= DIRED_SORT_DESCENDING; break; } } dired_sort_list(s); } #define MAX_COL_FILE_SIZE 32 static void dired_build_list(EditState *s, const char *path, const char *target) { DiredState *hs = s->mode_data; FindFileState *ffst; char filename[MAX_FILENAME_SIZE]; char line[1024], buf[1024]; const char *p; struct stat st; int ct, len, index; StringItem *item; /* free previous list, if any */ dired_free(s); /* CG: should make absolute ? */ canonicalize_path(hs->path, sizeof(hs->path), path); eb_set_filename(s->b, hs->path); s->b->flags |= BF_DIRED; ffst = find_file_open(hs->path, "*"); /* Should scan directory/filespec before computing lines to adjust * filename gutter width */ while (!find_file_next(ffst, filename, sizeof(filename))) { if (lstat(filename, &st) < 0) continue; p = get_basename(filename); #if 1 /* CG: bad idea, but causes spurious bugs */ /* exclude redundant '.' and '..' */ if (strequal(p, ".") || strequal(p, "..")) continue; #endif pstrcpy(line, sizeof(line), p); ct = 0; if (S_ISDIR(st.st_mode)) { ct = '/'; } else if (S_ISFIFO(st.st_mode)) { ct = '|'; } else if (S_ISSOCK(st.st_mode)) { ct = '='; } else if (S_ISLNK(st.st_mode)) { ct = '@'; } else if ((st.st_mode & 0111) != 0) { ct = '*'; } if (ct) { buf[0] = ct; buf[1] = '\0'; pstrcat(line, sizeof(line), buf); } /* pad with ' ' */ len = strlen(line); while (len < MAX_COL_FILE_SIZE) line[len++] = ' '; line[len] = '\0'; /* add file size or file info */ if (S_ISREG(st.st_mode)) { snprintf(buf, sizeof(buf), "%9ld", (long)st.st_size); } else if (S_ISDIR(st.st_mode)) { snprintf(buf, sizeof(buf), "%9s", "