Mercurial > hg > xemacs-beta
diff src/dired.c @ 0:376386a54a3c r19-14
Import from CVS: tag r19-14
author | cvs |
---|---|
date | Mon, 13 Aug 2007 08:45:50 +0200 |
parents | |
children | ac2d302a0011 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/dired.c Mon Aug 13 08:45:50 2007 +0200 @@ -0,0 +1,782 @@ + /* Lisp functions for making directory listings. + Copyright (C) 1985, 1986, 1992, 1993, 1994 Free Software Foundation, Inc. + +This file is part of XEmacs. + +XEmacs is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation; either version 2, or (at your option) any +later version. + +XEmacs 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 General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with XEmacs; see the file COPYING. If not, write to +the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. */ + +/* Synched up with: FSF 19.30. */ + +#include <config.h> +#include "lisp.h" + +#include "buffer.h" +#include "commands.h" +#include "elhash.h" +#include "regex.h" + +#include "sysfile.h" +#include "sysdir.h" + +Lisp_Object Vcompletion_ignored_extensions; + +Lisp_Object Qdirectory_files; +Lisp_Object Qfile_name_completion; +Lisp_Object Qfile_name_all_completions; +Lisp_Object Qfile_attributes; + +DEFUN ("directory-files", Fdirectory_files, Sdirectory_files, 1, 5, 0 /* +Return a list of names of files in DIRECTORY. +There are four optional arguments: +If FULL is non-nil, absolute pathnames of the files are returned. +If MATCH is non-nil, only pathnames containing that regexp are returned. +If NOSORT is non-nil, the list is not sorted--its order is unpredictable. + NOSORT is useful if you plan to sort the result yourself. +If FILES-ONLY is the symbol t, then only the \"files\" in the directory + will be returned; subdirectories will be excluded. If FILES-ONLY is not + nil and not t, then only the subdirectories will be returned. Otherwise, + if FILES-ONLY is nil (the default) then both files and subdirectories will + be returned. +*/ ) + (dirname, full, match, nosort, files_only) + Lisp_Object dirname, full, match, nosort, files_only; +{ + /* This function can GC */ + DIR *d; + Bytecount dirname_length; + Lisp_Object list, name, dirfilename = Qnil; + Lisp_Object handler; + struct re_pattern_buffer *bufp; + + char statbuf [MAXNAMLEN+2]; + char *statbuf_tail; + Lisp_Object tail_cons; + char slashfilename[MAXNAMLEN+2]; + char *filename = slashfilename; + + struct gcpro gcpro1, gcpro2, gcpro3, gcpro4, gcpro5; + + /* #### Needs more gcpro's */ + GCPRO5 (dirname, match, files_only, tail_cons, dirfilename); + + /* If the file name has special constructs in it, + call the corresponding file handler. */ + handler = Ffind_file_name_handler (dirname, Qdirectory_files); + if (!NILP (handler)) + { + UNGCPRO; + if (!NILP (files_only)) + return call6 (handler, Qdirectory_files, dirname, full, match, nosort, + files_only); + else + return call5 (handler, Qdirectory_files, dirname, full, match, + nosort); + } + + dirname = Fexpand_file_name (dirname, Qnil); + dirfilename = Fdirectory_file_name (dirname); + + { + /* XEmacs: this should come before the opendir() because it might error. */ + Lisp_Object name_as_dir = Ffile_name_as_directory (dirname); + CHECK_STRING (name_as_dir); + memcpy (statbuf, ((char *) string_data (XSTRING (name_as_dir))), + string_length (XSTRING (name_as_dir))); + statbuf_tail = statbuf + string_length (XSTRING (name_as_dir)); + } + + /* XEmacs: this should come after Ffile_name_as_directory() to avoid + potential regexp cache smashage. This should come before the + opendir() because it might signal an error. + */ + if (!NILP (match)) + { + CHECK_STRING (match); + + /* MATCH might be a flawed regular expression. Rather than + catching and signalling our own errors, we just call + compile_pattern to do the work for us. */ +#ifdef VMS + bufp = + compile_pattern (match, 0, + (char *) MIRROR_DOWNCASE_TABLE_AS_STRING + (XBUFFER (Vbuffer_defaults)), 0, ERROR_ME); +#else + bufp = compile_pattern (match, 0, 0, 0, ERROR_ME); +#endif + } + + /* Now *bufp is the compiled form of MATCH; don't call anything + which might compile a new regexp until we're done with the loop! */ + + /* Do this opendir after anything which might signal an error; if + an error is signalled while the directory stream is open, we + have to make sure it gets closed, and setting up an + unwind_protect to do so would be a pain. */ + d = opendir ((char *) string_data (XSTRING (dirfilename))); + if (! d) + report_file_error ("Opening directory", list1 (dirname)); + + list = Qnil; + tail_cons = Qnil; + dirname_length = string_length (XSTRING (dirname)); +#ifndef VMS + if (dirname_length == 0 + || !IS_ANY_SEP (string_byte (XSTRING (dirname), dirname_length - 1))) + { + *filename++ = DIRECTORY_SEP; + dirname_length++; + } +#endif /* VMS */ + + /* Loop reading blocks */ + while (1) + { + DIRENTRY *dp = readdir (d); + int len; + + if (!dp) break; + len = NAMLEN (dp); + if (DIRENTRY_NONEMPTY (dp)) + { + int result; + Lisp_Object oinhibit_quit = Vinhibit_quit; + strncpy (filename, dp->d_name, len); + filename[len] = 0; + /* re_search can now QUIT, so prevent it to avoid + filedesc lossage */ + Vinhibit_quit = Qt; + result = (NILP (match) + || (0 <= re_search (bufp, filename, len, 0, len, 0))); + Vinhibit_quit = oinhibit_quit; + if (result) + { + if (!NILP (files_only)) + { + int dir_p; + struct stat st; + + memcpy (statbuf_tail, filename, len); + statbuf_tail [len] = 0; + + if (stat (statbuf, &st) < 0) + dir_p = 0; + else + dir_p = ((st.st_mode & S_IFMT) == S_IFDIR); + + if (EQ (files_only, Qt) && dir_p) + continue; + else if (!EQ (files_only, Qt) && !dir_p) + continue; + } + + if (!NILP (full)) + name = concat2 (dirname, build_string (slashfilename)); + else + name = make_string ((Bufbyte *) filename, len); + + if (NILP (tail_cons)) + { + list = list1 (name); + tail_cons = list; + } + else + { + XCDR (tail_cons) = list1 (name); + tail_cons = XCDR (tail_cons); + } + } + } + } + closedir (d); + UNGCPRO; + if (!NILP (nosort)) + return list; + return Fsort (Fnreverse (list), Qstring_lessp); +} + +static Lisp_Object file_name_completion (Lisp_Object file, + Lisp_Object dirname, + int all_flag, int ver_flag); + +DEFUN ("file-name-completion", Ffile_name_completion, Sfile_name_completion, + 2, 2, 0 /* +Complete file name FILE in directory DIR. +Returns the longest string common to all filenames in DIR +that start with FILE. +If there is only one and FILE matches it exactly, returns t. +Returns nil if DIR contains no name starting with FILE. + +Filenames which end with any member of `completion-ignored-extensions' +are not considered as possible completions for FILE unless there is no +other possible completion. `completion-ignored-extensions' is not applied +to the names of directories. +*/ ) + (file, dirname) + Lisp_Object file, dirname; +{ + /* This function can GC */ + Lisp_Object handler; + + /* If the directory name has special constructs in it, + call the corresponding file handler. */ + handler = Ffind_file_name_handler (dirname, Qfile_name_completion); + if (!NILP (handler)) + return call3 (handler, Qfile_name_completion, file, dirname); + + /* If the file name has special constructs in it, + call the corresponding file handler. */ + handler = Ffind_file_name_handler (file, Qfile_name_completion); + if (!NILP (handler)) + return call3 (handler, Qfile_name_completion, file, dirname); + + return file_name_completion (file, dirname, 0, 0); +} + +DEFUN ("file-name-all-completions", Ffile_name_all_completions, + Sfile_name_all_completions, 2, 2, 0 /* +Return a list of all completions of file name FILE in directory DIR. +These are all file names in directory DIR which begin with FILE. + +Filenames which end with any member of `completion-ignored-extensions' +are not considered as possible completions for FILE unless there is no +other possible completion. `completion-ignored-extensions' is not applied +to the names of directories. +*/ ) + (file, dirname) + Lisp_Object file, dirname; +{ + /* This function can GC */ + Lisp_Object handler; + + /* If the file name has special constructs in it, + call the corresponding file handler. */ + handler = Ffind_file_name_handler (dirname, Qfile_name_all_completions); + if (!NILP (handler)) + return call3 (handler, Qfile_name_all_completions, file, + dirname); + + return file_name_completion (file, dirname, 1, 0); +} + +static int +file_name_completion_stat (Lisp_Object dirname, DIRENTRY *dp, + struct stat *st_addr) +{ + Bytecount len = NAMLEN (dp); + Bytecount pos = string_length (XSTRING (dirname)); + int value; + char *fullname = (char *) alloca (len + pos + 2); + + memcpy (fullname, string_data (XSTRING (dirname)), pos); +#ifndef VMS + if (!IS_DIRECTORY_SEP (fullname[pos - 1])) + fullname[pos++] = DIRECTORY_SEP; +#endif + + memcpy (fullname + pos, dp->d_name, len); + fullname[pos + len] = 0; + +#ifdef S_IFLNK + /* We want to return success if a link points to a nonexistent file, + but we want to return the status for what the link points to, + in case it is a directory. */ + value = lstat (fullname, st_addr); + if (S_ISLNK (st_addr->st_mode)) + stat (fullname, st_addr); +#else + value = stat (fullname, st_addr); +#endif + return (value); +} + +static Lisp_Object +file_name_completion (Lisp_Object file, Lisp_Object dirname, int all_flag, + int ver_flag) +{ + /* This function can GC */ + DIR *d = 0; + int matchcount = 0; + Lisp_Object bestmatch = Qnil; + Charcount bestmatchsize = 0; + struct stat st; + int passcount; + int speccount = specpdl_depth (); + Charcount file_name_length; + DIRENTRY *((*readfunc) (DIR *)) = readdir; + struct gcpro gcpro1, gcpro2, gcpro3; + + GCPRO3 (file, dirname, bestmatch); + + CHECK_STRING (file); + +#ifdef VMS + /* Filename completion on VMS ignores case, since VMS filesys does. */ + specbind (Qcompletion_ignore_case, Qt); + + if (ver_flag) + readfunc = readdirver; +#endif /* VMS */ + +#ifdef FILE_SYSTEM_CASE + file = FILE_SYSTEM_CASE (file); +#endif + dirname = Fexpand_file_name (dirname, Qnil); + bestmatch = Qnil; + file_name_length = string_char_length (XSTRING (file)); + + /* With passcount = 0, ignore files that end in an ignored extension. + If nothing found then try again with passcount = 1, don't ignore them. + If looking for all completions, start with passcount = 1, + so always take even the ignored ones. + + ** It would not actually be helpful to the user to ignore any possible + completions when making a list of them.** */ + + for (passcount = !!all_flag; NILP (bestmatch) && passcount < 2; passcount++) + { + d = opendir ((char *) + string_data (XSTRING (Fdirectory_file_name (dirname)))); + if (!d) + report_file_error ("Opening directory", list1 (dirname)); + + /* Loop reading blocks */ + /* (att3b compiler bug requires do a null comparison this way) */ + while (1) + { + DIRENTRY *dp; + Bytecount len; + /* scmp() works in characters, not bytes, so we have to compute + this value: */ + Charcount cclen; + int directoryp; + int ignored_extension_p = 0; + Bufbyte *d_name; + + dp = (*readfunc) (d); + if (!dp) break; + + d_name = (Bufbyte *) dp->d_name; + len = NAMLEN (dp); + /* mrb: #### FIX: The Name must be converted using + file-name-coding system or some such. At least this + change allows the saving of files in directories with + Japanese file names */ + /*cclen = bytecount_to_charcount (d_name, len);*/ + cclen = len; + + /* Can't just use QUIT because we have to make sure the file + descriptor gets closed. */ + if (QUITP) + { + closedir (d); + signal_quit (); + } + + if (! DIRENTRY_NONEMPTY (dp) + || cclen < file_name_length + || 0 <= scmp (d_name, + string_data (XSTRING (file)), + file_name_length)) + continue; + + if (file_name_completion_stat (dirname, dp, &st) < 0) + continue; + + directoryp = ((st.st_mode & S_IFMT) == S_IFDIR); + if (directoryp) + { +#ifndef TRIVIAL_DIRECTORY_ENTRY +#define TRIVIAL_DIRECTORY_ENTRY(n) (!strcmp (n, ".") || !strcmp (n, "..")) +#endif + /* "." and ".." are never interesting as completions, but are + actually in the way in a directory contains only one file. */ + if (!passcount && TRIVIAL_DIRECTORY_ENTRY (dp->d_name)) + continue; + } + else + { + /* Compare extensions-to-be-ignored against end of this file name */ + /* if name is not an exact match against specified string */ + if (!passcount && cclen > file_name_length) + { + Lisp_Object tem; + /* and exit this for loop if a match is found */ + for (tem = Vcompletion_ignored_extensions; + CONSP (tem); + tem = XCDR (tem)) + { + Lisp_Object elt = XCAR (tem); + Charcount skip; + + if (!STRINGP (elt)) continue; + skip = cclen - string_char_length (XSTRING (elt)); + if (skip < 0) continue; + + if (0 > scmp (charptr_n_addr (d_name, skip), + string_data (XSTRING (elt)), + string_char_length (XSTRING (elt)))) + { + ignored_extension_p = 1; + break; + } + } + } + } + + /* If an ignored-extensions match was found, + don't process this name as a completion. */ + if (!passcount && ignored_extension_p) + continue; + + if (!passcount && regexp_ignore_completion_p (d_name, Qnil, 0, len)) + continue; + + /* Update computation of how much all possible completions match */ + matchcount++; + + if (all_flag || NILP (bestmatch)) + { + Lisp_Object name = Qnil; + struct gcpro ngcpro1; + NGCPRO1 (name); + /* This is a possible completion */ + if (directoryp) + { + /* This completion is a directory; make it end with '/' */ + name = Ffile_name_as_directory + /* make_string (d_name, len); */ + (make_ext_string (d_name, len, FORMAT_BINARY)); + } + else + /* name = make_string (d_name, len) */ + name = make_ext_string (d_name, len, FORMAT_BINARY); + if (all_flag) + { + bestmatch = Fcons (name, bestmatch); + } + else + { + bestmatch = name; + bestmatchsize = string_char_length (XSTRING (name)); + } + NUNGCPRO; + } + else + { + Charcount compare = min (bestmatchsize, cclen); + Bufbyte *p1 = string_data (XSTRING (bestmatch)); + Bufbyte *p2 = d_name; + Charcount matchsize = scmp (p1, p2, compare); + + if (matchsize < 0) + matchsize = compare; + if (completion_ignore_case) + { + /* If this is an exact match except for case, + use it as the best match rather than one that is not + an exact match. This way, we get the case pattern + of the actual match. */ + if ((matchsize == len + && matchsize + !!directoryp + < string_char_length (XSTRING (bestmatch))) + || + /* If there is no exact match ignoring case, + prefer a match that does not change the case + of the input. */ + (((matchsize == len) + == + (matchsize + !!directoryp + == string_char_length (XSTRING (bestmatch)))) + /* If there is more than one exact match aside from + case, and one of them is exact including case, + prefer that one. */ + && 0 > scmp_1 (p2, string_data (XSTRING (file)), + file_name_length, 0) + && 0 <= scmp_1 (p1, string_data (XSTRING (file)), + file_name_length, 0))) + { + /* bestmatch = make_string (d_name, len); */ /* mrb */ + bestmatch = make_ext_string (d_name, len, FORMAT_BINARY); + if (directoryp) + bestmatch = + Ffile_name_as_directory (bestmatch); + } + } + + /* If this dirname all matches, + see if implicit following slash does too. */ + if (directoryp + && compare == matchsize + && bestmatchsize > matchsize + && IS_ANY_SEP (charptr_emchar_n (p1, matchsize))) + matchsize++; + bestmatchsize = matchsize; + } + } + closedir (d); + } + + unbind_to (speccount, Qnil); + + UNGCPRO; + + if (all_flag || NILP (bestmatch)) + return bestmatch; + if (matchcount == 1 && bestmatchsize == file_name_length) + return Qt; + return Fsubstring (bestmatch, make_int (0), make_int (bestmatchsize)); +} + + +Lisp_Object +make_directory_hash_table (char *path) +{ + DIR *d; + DIRENTRY *dp; + Bytecount len; + Lisp_Object hash = make_lisp_hashtable (100, HASHTABLE_NONWEAK, + HASHTABLE_EQUAL); + if ((d = opendir (path))) + { + while ((dp = readdir (d))) + { + len = NAMLEN (dp); + if (DIRENTRY_NONEMPTY (dp)) + Fputhash (make_string ((Bufbyte *) dp->d_name, len), Qt, hash); + } + closedir (d); + } + return hash; +} + +#ifdef VMS + +DEFUN ("file-name-all-versions", Ffile_name_all_versions, + Sfile_name_all_versions, 2, 2, 0 /* +Return a list of all versions of file name FILE in directory DIR. +*/ ) + (file, dirname) + Lisp_Object file, dirname; +{ + /* This function can GC */ + return file_name_completion (file, dirname, 1, 1); +} + +DEFUN ("file-version-limit", Ffile_version_limit, Sfile_version_limit, 1, 1, 0 /* +Return the maximum number of versions allowed for FILE. +Returns nil if the file cannot be opened or if there is no version limit. +*/ ) + (filename) + Lisp_Object filename; +{ + /* This function can GC */ + Lisp_Object retval; + struct FAB fab; + struct RAB rab; + struct XABFHC xabfhc; + int status; + + filename = Fexpand_file_name (filename, Qnil); + CHECK_STRING (filename); + fab = cc$rms_fab; + xabfhc = cc$rms_xabfhc; + fab.fab$l_fna = string_data (XSTRING (filename)); + fab.fab$b_fns = strlen (fab.fab$l_fna); + fab.fab$l_xab = (char *) &xabfhc; + status = sys$open (&fab, 0, 0); + if (status != RMS$_NORMAL) /* Probably non-existent file */ + return Qnil; + sys$close (&fab, 0, 0); + if (xabfhc.xab$w_verlimit == 32767) + return Qnil; /* No version limit */ + else + return make_int (xabfhc.xab$w_verlimit); +} + +#endif /* VMS */ + + +Lisp_Object +wasteful_word_to_lisp (unsigned int item) +{ + /* Compatibility: in other versions, file-attributes returns a LIST + of two 16 bit integers... */ + Lisp_Object cons = word_to_lisp (item); + XCDR (cons) = Fcons (XCDR (cons), Qnil); + return cons; +} + +DEFUN ("file-attributes", Ffile_attributes, Sfile_attributes, 1, 1, 0 /* +Return a list of attributes of file FILENAME. +Value is nil if specified file cannot be opened. +Otherwise, list elements are: + 0. t for directory, string (name linked to) for symbolic link, or nil. + 1. Number of links to file. + 2. File uid. + 3. File gid. + 4. Last access time, as a list of two integers. + First integer has high-order 16 bits of time, second has low 16 bits. + 5. Last modification time, likewise. + 6. Last status change time, likewise. + 7. Size in bytes. (-1, if number is out of range). + 8. File modes, as a string of ten letters or dashes as in ls -l. + 9. t iff file's gid would change if file were deleted and recreated. +10. inode number. +11. Device number. + +If file does not exist, returns nil. +*/ ) + (filename) + Lisp_Object filename; +{ + /* This function can GC */ + Lisp_Object values[12]; + Lisp_Object dirname = Qnil; + struct stat s; + char modes[10]; + Lisp_Object handler; + struct gcpro gcpro1, gcpro2; + + GCPRO1 (filename); + filename = Fexpand_file_name (filename, Qnil); + + /* If the file name has special constructs in it, + call the corresponding file handler. */ + handler = Ffind_file_name_handler (filename, Qfile_attributes); + UNGCPRO; + if (!NILP (handler)) + return call2 (handler, Qfile_attributes, filename); + + if (lstat ((char *) string_data (XSTRING (filename)), &s) < 0) + return Qnil; + + GCPRO2 (filename, dirname); + +#ifdef BSD4_2 + dirname = Ffile_name_directory (filename); +#endif + +#ifdef MSDOS + { + char *tmpnam = + (char *) string_data (XSTRING (Ffile_name_nondirectory (filename))); + int l = strlen (tmpnam); + + if (l >= 5 + && S_ISREG (s.st_mode) + && (stricmp (&tmpnam[l - 4], ".com") == 0 + || stricmp (&tmpnam[l - 4], ".exe") == 0 + || stricmp (&tmpnam[l - 4], ".bat") == 0)) + { + s.st_mode |= S_IEXEC; + } + } +#endif /* MSDOS */ + + switch (s.st_mode & S_IFMT) + { + default: + values[0] = Qnil; + break; + case S_IFDIR: + values[0] = Qt; + break; +#ifdef S_IFLNK + case S_IFLNK: + values[0] = Ffile_symlink_p (filename); + break; +#endif + } + values[1] = make_int (s.st_nlink); + values[2] = make_int (s.st_uid); + values[3] = make_int (s.st_gid); + values[4] = wasteful_word_to_lisp (s.st_atime); + values[5] = wasteful_word_to_lisp (s.st_mtime); + values[6] = wasteful_word_to_lisp (s.st_ctime); + values[7] = make_int ((EMACS_INT) s.st_size); + /* If the size is out of range, give back -1. */ + /* #### Fix when Emacs gets bignums! */ + if (XINT (values[7]) != s.st_size) + XSETINT (values[7], -1); + filemodestring (&s, modes); + values[8] = make_string ((Bufbyte *) modes, 10); +#if defined (BSD4_2) || defined (BSD4_3) /* file gid will be dir gid */ + { + struct stat sdir; + + if (!NILP (dirname) && stat ((char *) string_data (XSTRING (dirname)), &sdir) == 0) + values[9] = (sdir.st_gid != s.st_gid) ? Qt : Qnil; + else /* if we can't tell, assume worst */ + values[9] = Qt; + } +#else /* file gid will be egid */ +#ifdef WINDOWSNT + values[9] = Qnil; /* sorry, no group IDs on NT */ +#else /* not WINDOWSNT */ + values[9] = (s.st_gid != getegid ()) ? Qt : Qnil; +#endif /* not WINDOWSNT */ +#endif /* BSD4_2 or BSD4_3 */ +#ifdef WINDOWSNT + /* Fill in the inode and device values specially...see nt.c. */ + if (!get_inode_and_device_vals (filename, &values[10], &values[11])) + { + UNGCPRO; + return Qnil; + } +#else /* not WINDOWSNT */ + values[10] = make_int (s.st_ino); + values[11] = make_int (s.st_dev); +#endif /* not WINDOWSNT */ + UNGCPRO; + return Flist (countof (values), values); +} + + +/************************************************************************/ +/* initialization */ +/************************************************************************/ + +void +syms_of_dired (void) +{ + defsymbol (&Qdirectory_files, "directory-files"); + defsymbol (&Qfile_name_completion, "file-name-completion"); + defsymbol (&Qfile_name_all_completions, "file-name-all-completions"); + defsymbol (&Qfile_attributes, "file-attributes"); + + defsubr (&Sdirectory_files); + defsubr (&Sfile_name_completion); +#ifdef VMS + defsubr (&Sfile_name_all_versions); + defsubr (&Sfile_version_limit); +#endif /* VMS */ + defsubr (&Sfile_name_all_completions); + defsubr (&Sfile_attributes); +} + +void +vars_of_dired (void) +{ + DEFVAR_LISP ("completion-ignored-extensions", &Vcompletion_ignored_extensions /* +*Completion ignores filenames ending in any string in this list. +This variable does not affect lists of possible completions, +but does affect the commands that actually do completions. +It is used by the functions `file-name-completion' and +`file-name-all-completions'. +*/ ); + Vcompletion_ignored_extensions = Qnil; +}