Article View: comp.sources.reviewed
Article #6v02i037: dired - (Ver. 1.8) directory editor in C++, Part05/05
From: Mike Lijewski
Date: Wed, 28 Oct 1992 02:41
Date: Wed, 28 Oct 1992 02:41
1606 lines
50041 bytes
50041 bytes
Submitted-by: Mike Lijewski <lijewski@theory.tc.cornell.edu>
Posting-number: Volume 2, Issue 37
Archive-name: dired/part05
Supersedes: dired: Volume 2, Issue 7-12
#! /bin/sh
# This is a shell archive. Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file". To overwrite existing
# files, type "sh file -c". You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g.. If this archive is complete, you
# will see the following message at the end:
# "End of archive 5 (of 5)."
# Contents: version.h utilities.C
# Wrapped by lijewski@xtesoc2 on Mon Oct 5 14:53:45 1992
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'version.h' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'version.h'\"
else
echo shar: Extracting \"'version.h'\" \(1191 characters\)
sed "s/^X//" >'version.h' <<'END_OF_FILE'
X/*
X** version.h - where our version number is defined
X**
X** version.h %M% %I% Delta'd: %U% %G% Mike Lijewski, CNSF
X**
X** Copyright (c) 1991, 1992 Cornell University
X** All rights reserved.
X**
X** Redistribution and use in source and binary forms are permitted
X** provided that: (1) source distributions retain this entire copyright
X** notice and comment, and (2) distributions including binaries display
X** the following acknowledgement: ``This product includes software
X** developed by Cornell University'' in the documentation or other
X** materials provided with the distribution and in all advertising
X** materials mentioning features or use of this software. Neither the
X** name of the University nor the names of its contributors may be used
X** to endorse or promote products derived from this software without
X** specific prior written permission.
X**
X** THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
X** IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
X** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
X*/
X
X#ifndef _VERSION_H
X#define _VERSION_H
X
X#include "dired.h"
X
Xextern const char *const version = "Dired Version 1.8";
X
X#endif
END_OF_FILE
if test 1191 -ne `wc -c <'version.h'`; then
echo shar: \"'version.h'\" unpacked with wrong size!
fi
# end of 'version.h'
fi
if test -f 'utilities.C' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'utilities.C'\"
else
echo shar: Extracting \"'utilities.C'\" \(43137 characters\)
sed "s/^X//" >'utilities.C' <<'END_OF_FILE'
X/*
X** utilities.C - utility functions
X**
X** utilities.C 1.98 Delta\'d: 14:27:42 10/5/92 Mike Lijewski, CNSF
X**
X** Copyright \(c\) 1991, 1992 Cornell University
X** All rights reserved.
X**
X** Redistribution and use in source and binary forms are permitted
X** provided that: \(1\) source distributions retain this entire copyright
X** notice and comment, and \(2\) distributions including binaries display
X** the following acknowledgement: ``This product includes software
X** developed by Cornell University\'\' in the documentation or other
X** materials provided with the distribution and in all advertising
X** materials mentioning features or use of this software. Neither the
X** name of the University nor the names of its contributors may be used
X** to endorse or promote products derived from this software without
X** specific prior written permission.
X**
X** THIS SOFTWARE IS PROVIDED ``AS IS\'\' AND WITHOUT ANY EXPRESS OR
X** IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
X** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
X*/
X
X
X#include <ctype.h>
X
X#ifndef _IBMR2
X#include <libc.h>
X#endif /*_IBMR2*/
X
X#include <osfcn.h>
X#include <pwd.h>
X#include <signal.h>
X#include <stdlib.h>
X
X#ifdef _IBMR2
X#include <sys/access.h>
X#endif /*_IBMR2*/
X
X#include <sys/ioctl.h>
X#include <sys/stat.h>
X#include <sys/types.h>
X#include <sys/wait.h>
X
X#ifdef COMPLETION
X#include <dirent.h>
X#endif
X
X#include <string.h>
X#include <sys/errno.h>
X#include <unistd.h>
X
X#include "dired.h"
X#include "display.h"
X#include "keys.h"
X
X/*
X** expand_tilde - expects a string of the form "~ ...".
X** Returns a new string in volatile storage
X** with the user\'s home directory in place of the `~\'.
X** The user\'s home directory is always appended
X** in the form: "/usr/staff/mjlx"; a slash is not added to
X** the end of the home directory string. Returns the original
X** string if we cannot get the user\'s home directory. The
X** user should not attempt to delete the return value.
X*/
X
Xconst char *expand_tilde(const char *str)
X{
X static char *home = getenv("HOME");
X if (home == NULL)
X {
X struct passwd *user = getpwuid(getuid());
X if (user == NULL) return str;
X home = user->pw_dir;
X }
X if (*str != '~') return str;
X static String expansion;
X expansion = String(home) + (str + 1);
X return expansion;
X}
X
X/*
X** fgetline\(FILE *f\) - returns a pointer to the start of a line read
X** from fp, or the null pointer if we hit eof or get
X** an error from fgets\(\). Exits if new\(\) fails.
X** Strips the newline from the line.
X** Caller should free memory if desired.
X*/
X
Xstatic const int FGETLINE_BUFSIZE = 80; // chunksize for calls to new\(\)
X
Xchar *fgetline(FILE *fp)
X{
X char *buffer = new char[FGETLINE_BUFSIZE];
X
X char *result = fgets(buffer, FGETLINE_BUFSIZE, fp);
X if (result == 0)
X {
X //
X // either error or at eof
X //
X DELETE buffer;
X return 0;
X }
X
X if (buffer[strlen(buffer)-1] != '\n' && !feof(fp))
X {
X //
X // longer line than buffer can hold
X //
X char *restofline = fgetline(fp);
X
X if (restofline == 0) return 0; // eof or error
X
X char *longline = new char[strlen(buffer) + strlen(restofline) + 1];
X (void)strcat(strcpy(longline, buffer), restofline);
X
X DELETE restofline;
X DELETE buffer;
X
X if (longline[strlen(longline) - 1] == '\n')
X longline[strlen(longline) - 1] = '\0';
X
X return longline;
X }
X else
X {
X if (buffer[strlen(buffer) - 1] == '\n')
X buffer[strlen(buffer) - 1] = '\0';
X
X return buffer;
X }
X}
X
X/*
X** current_directory - This routine tries to determine the full pathname
X** of the current directory. The pointer returned is
X** new\(\)\'d storage.
X*/
X
Xchar *current_directory()
X{
X const int chunksize = 50;
X int size = chunksize;
X char *dir = new char[size];
X while (getcwd(dir, size) == 0)
X if (errno == ERANGE)
X {
X DELETE dir;
X dir = new char[size += chunksize];
X continue;
X }
X else
X //
X // Must have got an EACCES.
X //
X return 0;
X return dir;
X}
X
X/*
X** display_string - prints a string to the given the display, guaranteeing not
X** to print more than columns\(\) characters. If the string
X** exceeds the width of the window, a `!\' is placed in
X** the final column. Can be called with or without the
X** length of the string to be printed. In most places in
X** the code we know the exact length of the strings we
X** wish to print. Note that `len\' has a default value
X** of zero defined by the declaration in "dired.h".
X** We never call this when trying to write to the last
X** row on the screen. That is the dominion of message\(\).
X*/
X
Xvoid display_string(const char *str, size_t len)
X{
X size_t string_length = len == 0 ? strlen(str) : len;
X
X if (string_length < columns())
X {
X (void)fputs(str, stdout);
X cursor_wrap();
X }
X else if (string_length > columns())
X {
X (void)printf("%*.*s%c", columns() - 1, columns() - 1, str, '!');
X if (!AM || XN) cursor_wrap();
X }
X else
X {
X (void)fputs(str, stdout);
X if (!AM || XN) cursor_wrap();
X }
X}
X
X/*
X** get_directory_listing - Get a long listing of the given directory.
X** Returns 0 if we got other than a
X** "memory exhausted" error.
X*/
X
XDirList *get_directory_listing(char *dirname)
X{
X message("Reading directory ... ");
X
X String cmd = String(ls_cmd[the_sort_order()]) + dirname + " 2>/dev/null";
X FILE *fp = popen(cmd, "r");
X if (fp == 0) return 0;
X
X DirList *directory = new DirList(dirname);
X
X //
X // discard lines of the form:
X //
X // total 1116
X //
X char *line = fgetline(fp);
X DELETE line;
X if (fp == 0) return 0;
X
X while((line = fgetline(fp)) != 0) directory->add(new DirLine(&line));
X
X message("Reading directory ... done");
X
X if (feof(fp) && !ferror(fp))
X {
X (void)pclose(fp);
X return directory;
X }
X else
X return 0;
X}
X
X/*
X** error - Prints error message so it can be read. This is the error
X** function we call once we\'ve initialized the display.
X*/
X
Xvoid error(const char *str)
X{
X clear_display();
X move_cursor(rows()-1, 0);
X (void) printf(str);
X cursor_wrap();
X synch_display();
X term_display();
X exit(EXIT_FAILURE);
X}
X
Xvoid error(const char *fmt, int index)
X{
X clear_display();
X move_cursor(rows()-1, 0);
X (void) printf(fmt, index);
X cursor_wrap();
X synch_display();
X term_display();
X exit(EXIT_FAILURE);
X}
X
Xvoid error(const char *fmt, const char *file, int line)
X{
X clear_display();
X move_cursor(rows()-1, 0);
X (void) printf(fmt, file, line);
X cursor_wrap();
X synch_display();
X term_display();
X exit(EXIT_FAILURE);
X}
X
Xvoid error(const char *fmt, const char *file, int line, const char *str)
X{
X clear_display();
X move_cursor(rows()-1, 0);
X (void) printf(fmt, file, line, str);
X cursor_wrap();
X synch_display();
X term_display();
X exit(EXIT_FAILURE);
X}
X
X/*
X** update_modeline - this routine concatenates the two strings
X** into the modeline. The modeline
X** is displayed in standout mode if possible.
X** We never put more than columns\(\) characters into
X** the modeline. The modeline is the penultimate
X** line on the terminal screen. It does not
X** synch the display. If head == tail == 0, we
X** just display the old modeline. This happens
X** if for some reason we had to clear the screen.
X*/
X
Xvoid update_modeline(const char *head, const char *tail)
X{
X static char *oldline;
X move_to_modeline();
X enter_standout_mode();
X
X if (head == 0 && tail == 0)
X {
X //
X // Redisplay old modeline.
X //
X (void)fputs(oldline, stdout);
X end_standout_mode();
X return;
X }
X
X int len = (int)strlen(head);
X char *modeline = new char[columns() + 1];
X (void)strncpy(modeline, head, columns());
X modeline[columns()] = 0; // ensure it\'s null-terminated
X
X if (len < columns())
X {
X //
X // Write exactly columns\(\) characters to modeline.
X //
X for (int i = len; i < columns() - 1 && tail && *tail; i++, tail++)
X modeline[i] = *tail;
X if (i < columns() - 1)
X {
X modeline[i++] = ' ';
X for (; i < columns(); i++) modeline[i] = '-';
X }
X else if (tail && *tail)
X //
X // The string was overly long. Put a \'!\' in the last space
X // on the modeline to signify truncation.
X //
X modeline[columns() - 1] = '!';
X else
X //
X // Here len == columns\(\)-1 && there is nothing else in tail.
X //
X modeline[columns() - 1] = ' ';
X }
X else if (len > columns())
X modeline[columns() - 1] = '!';
X
X if (oldline)
X {
X update_screen_line(oldline, modeline, rows() - 2);
X DELETE oldline;
X }
X else
X (void)fputs(modeline, stdout);
X
X oldline = modeline;
X end_standout_mode();
X}
X
X/*
X** is_directory - returns non-zero if a directory, otherwise 0.
X** Also returns zero on error.
X*/
X
Xint is_directory(const char *dir)
X{
X struct stat stbuf;
X if (stat(dir, &stbuf) < 0) return 0;
X return S_ISDIR(stbuf.st_mode);
X}
X
X/*
X** is_regular_file - returns non-zero if a regular file, otherwise 0.
X** Also returns zero on error.
X*/
X
Xint is_regular_file(const char *file)
X{
X struct stat stbuf;
X if (stat(file, &stbuf) < 0) return 0;
X return S_ISREG(stbuf.st_mode);
X}
X
X/*
X** read_and_exec_perm - returns non-zero if we have read and execute
X** permission on the directory, otherwise 0.
X** Returns 0 on error.
X*/
X
Xint read_and_exec_perm(const char *dir)
X{
X return access(dir, R_OK | X_OK) == -1 ? 0 : 1;
X}
X
X/*
X** Find the column position of the first character of the filename
X** in the current line of the given DirList.
X**
X** The straight-forward way to do this is to walk the string from it\'s
X** tail to it\'s head until we hit some whitespace. This presumes
X** that filenames don\'t contain whitespace. The one special case
X** to worry about is if the file is a symbolic link. In that case we\'ll
X** have a filename field entry of the form
X**
X** Xm -> /usr/lpp/include/Xm
X*/
X
Xint goal_column(DirList *l)
X{
X DirLine *line = l->currLine();
X const char *tmp;
X
X#ifndef NO_SYMLINKS
X if ((tmp = strstr(line->line(), " -> ")) != 0)
X //
X // We have a symbolic link.
X //
X --tmp;
X else
X#endif
X tmp = line->line() + line->length() - 1;
X
X while(!isspace(*tmp)) --tmp;
X
X return tmp - line->line() + 1;
X}
X
X/*
X** get_file_name - returns the filename of the current line of the DirList
X** in volatile storage.
X**
X** If we have a symbolic link, we return the link not
X** the file pointed to.
X*/
X
Xconst char *get_file_name(DirList *dl)
X{
X static String file;
X file = &(dl->currLine()->line())[goal_column(dl)];
X
X //
X // Do we have a symbolic link?
X //
X char *result = strstr(file, " -> ");
X if (result) *result = '\0';
X
X return file;
X}
X
X/*
X** redisplay - this routine redisplays the DirList at the top
X** of our stack. It assumes that the physical screen
X** has become corrupted, clearing each line before writing
X** to it.
X*/
X
Xvoid redisplay()
X{
X DirList *dl = dir_stack->top();
X
X DirLine *ln = dl->firstLine();
X cursor_home();
X for (int i = 0; i < rows() - 2 && ln; i++, ln = ln->next())
X {
X clear_to_end_of_line();
X display_string(ln->line(), ln->length());
X }
X move_cursor(i, 0);
X for (; i < rows() - 2; i++)
X {
X clear_to_end_of_line();
X cursor_down();
X }
X
X move_to_modeline();
X clear_to_end_of_line();
X update_modeline();
X clear_message_line();
X
X if (dl->currLine()->length() > columns())
X leftshift_current_line(dl);
X else
X move_cursor(dl->savedYPos(), dl->savedXPos());
X
X synch_display();
X}
X
X//
X// forward declaration
X//
Xstatic void adjust_window();
X
X/*
X** eat_a_character - displays the message in standout mode and waits
X** until a character is typed. Deals with SIGWINCH
X** and SIGTSTP
X*/
X
Xvoid eat_a_character(const char *msg)
X{
X enter_standout_mode();
X message(msg);
X end_standout_mode();
X char c;
X while (1)
X if (read(0, &c, 1) < 0 // assume fails only when errno == EINTR
X#ifdef SIGWINCH
X || win_size_changed
X#endif
X )
X {
X#ifdef SIGWINCH
X if (win_size_changed)
X {
X win_size_changed = 0;
X adjust_window();
X redisplay();
X }
X#endif
X //
X // Must redisplay the message.
X //
X enter_standout_mode();
X message(msg);
X end_standout_mode();
X }
X else
X return;
X}
X
X/*
X** read_from_keybd - reads a character from the keyboard. It only returns
X** when it\'s successfully read a character. So if we
X** get suspended while waiting to read a character, the
X** signal handler will redisplay the screen and we\'ll
X** still be here waiting to read a character. This is
X** only used by the main command loop so that we know
X** that redisplay\(\) will do all the necessary redisplay
X** operations.
X*/
X
Xint read_from_keybd()
X{
X char c;
X while(1)
X if (read(0, &c, 1) < 0 // assume fails only when errno == EINTR
X#ifdef SIGWINCH
X || win_size_changed
X#endif
X )
X {
X#ifdef SIGWINCH
X if (win_size_changed)
X {
X win_size_changed = 0;
X adjust_window();
X redisplay();
X }
X#endif
X }
X else
X return c;
X}
X
X/*
X** execute - executes command using exec\(2\). Returns 1 if the exec
X** went OK, otherwise it returns 0. If `closem\' is true, which
X** is the default, we close file descriptors 0, 1 and 2.
X*/
X
Xint execute(const char *file, const char *argv[], int closem)
X{
X int status;
X pid_t pid = fork();
X switch(pid)
X {
X case -1: // error
X return 0;
X case 0: // in the child
X if (closem)
X {
X (void)close(0);
X (void)close(1);
X (void)close(2);
X }
X execvp(file, (char *const *)argv);
X //
X // Exec failed.
X //
X exit(1);
X default: // in the parent
X waitpid(pid, &status, 0);
X return status == 0 ? 1 : 0;
X }
X}
X
X/*
X** exec_with_system - execute the passed command using system\(3\).
X** If prompt == 1, which is the default, we prompt for
X** a key before returning. We use system\(3\) so that
X** shell and environment variables will be expanded.
X*/
X
Xvoid exec_with_system(const char *cmd, int prompt)
X{
X unsetraw();
X unset_signals();
X system(cmd);
X set_signals();
X setraw();
X if (prompt) eat_a_character("Press Any Key to Continue");
X}
X
X#ifdef SIGWINCH
X
X#include <sys/ioctl.h>
X
X/*
X** adjust_window - called to adjust our window after getting a SIGWINCH
X*/
X
Xstatic void adjust_window()
X{
X#ifdef TIOCGWINSZ
X struct winsize w;
X if (ioctl(2, TIOCGWINSZ, (char *)&w) == 0 && w.ws_row > 0) LI = w.ws_row;
X if (ioctl(2, TIOCGWINSZ, (char *)&w) == 0 && w.ws_col > 0) CO = w.ws_col;
X
X //
X // Is current line still on the screen?
X //
X if (dir_stack->top()->savedYPos() >= rows()-2)
X {
X dir_stack->top()->setCurrLine(dir_stack->top()->firstLine());
X dir_stack->top()->saveYXPos(0, goal_column(dir_stack->top()));
X }
X
X // need to adjust lastLine\(\)
X DirLine *ln = dir_stack->top()->firstLine();
X for (int i = 0; i < rows()-2 && ln; i++, ln = ln->next()) ;
X ln ? dir_stack->top()->setLast(ln->prev()) :
X dir_stack->top()->setLast(dir_stack->top()->tail());
X#endif
X}
X#endif
X
X#ifdef COMPLETION
X
X/*
X** string_sort - this is the sort routine we pass to qsort\(\) for
X** sorting the table of possible completions.
X*/
X
Xstatic int string_sort(const void *a, const void *b)
X{
X return strcmp(*(char **)a, *(char **)b);
X}
X
X/*
X** complete - read directory `dir\' for any completions of `prefix\'.
X** Returns zero on error; if prefix is null; if there are no
X** completions; or if the completion is the prefix itself.
X** Else it returns a string in volatile space containing the
X** longest possible completion of `prefix\', minus the
X** characters in the prefix. Used only by prompt\(\).
X*/
X
Xchar *complete(const char *directory, const char *prefix)
X{
X static char *completion = 0; // our volatile storage
X size_t prefix_length = strlen(prefix);
X const int chunksize = 10;
X int tablesize = chunksize;
X int matches = 0; // number of matches of `prefix\' in `directory\'
X
X DIR *dirp = opendir(directory);
X if (dirp == NULL) return 0;
X
X char **completions = new char*[tablesize * sizeof(char *)];
X
X struct dirent *entry;
X for (entry = readdir(dirp); entry != NULL; entry = readdir(dirp))
X {
X if (strncmp(entry->d_name, prefix, prefix_length) == 0)
X {
X //
X // We\'ve got a match.
X //
X completions[matches] = new char[entry->d_namlen + 1];
X (void)strcpy(completions[matches++], entry->d_name);
X if (matches == tablesize)
X {
X //
X // Grow table.
X //
X tablesize += chunksize;
X char **newtable = new char*[tablesize * sizeof(char *)];
X for (int i = 0; i < tablesize - chunksize; i++)
X newtable[i] = completions[i];
X DELETE completions;
X completions = newtable;
X }
X }
X }
X
X //
X // I should really be testing the rc here, but so many of the
X // machines I\'ve tested have this improperly prototyped to void,
X // that it\'s best not to for the time being.
X //
X (void)closedir(dirp);
X
X if (matches == 0)
X {
X DELETE completions;
X return 0;
X }
X
X if (matches == 1)
X {
X if (strcmp(prefix, completions[0]) == 0)
X {
X //
X // If the completion matches our input prefix, we return 0.
X //
X DELETE completions[0];
X DELETE completions;
X return 0;
X }
X DELETE completion;
X
X completion = new char[strlen(completions[0]) - prefix_length +1];
X (void)strcpy(completion, completions[0] + prefix_length);
X
X DELETE completions[0];
X DELETE completions;
X
X return completion;
X }
X
X qsort(completions, matches, sizeof(char **), string_sort);
X
X //
X // the completion cannot be longer than the first sorted item
X //
X size_t maxlen = strlen(completions[0]);
X char *tmp = new char[maxlen + 1];
X (void)strcpy(tmp, prefix);
X size_t i = prefix_length;
X char c = completions[0][i];
X for (; i < maxlen; i++, c = completions[0][i])
X {
X int stop = 0;
X for (int j = 1; j < matches; j++)
X if (c != completions[j][i])
X {
X stop = 1;
X break;
X }
X if (stop) break;
X tmp[i] = c;
X }
X tmp[i] = 0; // nullify the string
X if (strcmp(prefix, tmp) == 0)
X {
X //
X // If the completion matches our input prefix, we return 0.
X //
X DELETE tmp;
X for (i = 0; i < matches; i++) DELETE completions[i];
X DELETE completions;
X
X return 0;
X }
X DELETE completion;
X completion = new char[maxlen - prefix_length + 1];
X (void)strcpy(completion, tmp + prefix_length);
X
X DELETE tmp;
X
X for (i = 0; i < matches; i++) DELETE completions[i];
X DELETE completions;
X
X return completion;
X}
X
X#endif /*COMPLETION*/
X
X/*
X** prompt - displays `msg\' prompt and then collects the response.
X** The keys of the response are echoed as they\'re collected.
X** A response of 0 indicates that the command was aborted.
X** A response can contain any graphical character.
X** C-G will abort out of a prompt; Backspace works as expected.
X** Carriage return indicates the end of response.
X** Non-graphical characters are ignored. If do_completion != 0,
X** with do_completon == 0 by default, we do filename completion
X** on the TAB character. The response is kept in volatile storage,
X** so if the client needs to save it, they must make a copy of it.
X*/
X
X#ifdef COMPLETION
Xconst char *prompt(const char *msg, int do_completion)
X#else
Xconst char *prompt(const char *msg)
X#endif
X{
X static char *response; // our volatile storage
X
X size_t written = 0; // number of characters written to message line
X const char *abort_msg = "(C-g to Abort) ";
X String nmsg = String(abort_msg) + msg;
X size_t len = nmsg.length();
X
X move_to_message_line(); // it will have been already cleared by get_key\(\)
X
X if (len < columns())
X {
X (void)fputs(nmsg, stdout);
X written = len;
X }
X else
X {
X // Leave space for columns\(\)/2 + 1 characters.
X (void)fputs((const char *)nmsg + (len-columns()/2+1), stdout);
X written = columns()/2 - 1;
X }
X synch_display();
X
X //
X // We never echo into the last position in the message window.
X //
X size_t space_available = columns() - written; // available spaces in line
X
X DELETE response; // release any volatile storage
X response = new char[space_available + 1];
X size_t pos = 0; // index of next character in `response\'
X
X char key;
X for (;;)
X {
X if (read(0, &key, 1) < 0 // assume fails only when errno == EINTR
X#ifdef SIGWINCH
X || win_size_changed
X#endif
X )
X {
X#ifdef SIGWINCH
X if (win_size_changed)
X {
X win_size_changed = 0;
X adjust_window();
X redisplay();
X }
X#endif
X //
X // On a SIGTSTP the signal handler does the redisplay\(\),
X // so all we have to worry about is getting the prompt
X // redisplayed correctly.
X // Must make sure total output is less than screen width.
X //
X clear_message_line();
X response[pos] = 0;
X if (pos + len < columns())
X {
X //
X // Output message and response-to-date.
X //
X (void)fputs(nmsg, stdout);
X (void)fputs(response, stdout);
X space_available = columns() - pos - len;
X }
X else if (pos < columns())
X {
X //
X // Display the response.
X //
X (void)fputs(response, stdout);
X space_available = columns() - strlen(response);
X }
X else
X {
X //
X // Display the backend of the response
X //
X (void)fputs(&response[pos - columns()/2 + 1], stdout);
X space_available = columns()/2 + 1;
X }
X synch_display();
X }
X else if (isprint(key))
X {
X //
X // Echo character to message window and wait for another.
X //
X response[pos++] = key;
X space_available--;
X if (!space_available)
X {
X //
X // Need to allocate more room for the response.
X // Note that strlen\(response\) == pos
X //
X space_available = columns()/2 + 1;
X char *nresponse = new char[pos + space_available + 1];
X response[pos] = 0; // stringify response
X (void)strcpy(nresponse, response);
X DELETE response;
X response = nresponse;
X //
X // Shift prompt in message window so we
X // always have the end in view to which we\'re
X // adding characters as they\'re typed.
X //
X clear_message_line();
X (void)fputs(&response[pos - columns()/2 + 1], stdout);
X }
X else
X putchar(key);
X synch_display();
X }
X else
X switch (key)
X {
X#ifdef COMPLETION
X case KEY_TAB: // perform filename completion
X if (do_completion == 0 || pos == 0)
X {
X ding();
X break;
X }
X
X response[pos] = 0; // stringify response
X char *last = strrchr(response, '/');
X const char *current_directory = dir_stack->top()->name();
X char *completion = 0;
X
X if (response[0] == '~')
X {
X //
X // Expand the tilde before trying the completion,
X // but there is no use trying the completion if we don\'t
X // have a string of the form: "~/.*".
X //
X if (response[1] != '/')
X {
X ding();
X break;
X }
X String head(response);
X char *expansion = (char *) expand_tilde(head);
X char *last = strrchr(expansion, '/');
X expansion[last - expansion] = 0;
X completion = complete(expansion, last + 1);
X }
X else if (last == NULL)
X //
X // Filename is relative to our current directory.
X //
X completion = complete(current_directory, response);
X else
X {
X //
X // The response contains a slash.
X //
X if (response[0] == '/')
X {
X //
X // It\'s an absolute path.
X //
X if (pos == 1)
X {
X ding();
X break;
X }
X if (response == last)
X //
X // The first slash is also the last.
X //
X completion = complete("/", last + 1);
X else
X {
X String head(response);
X head[last - response] = 0;
X completion = complete(head, last + 1);
X }
X }
X else
X {
X //
X // It\'s relative to our current directory.
X //
X String head = String(current_directory)+ "/" +response;
X head[strlen(current_directory)+last-response+1] = 0;
X completion = complete(head, last + 1);
X }
X }
X if (completion == 0)
X {
X ding();
X break;
X }
X
X size_t clen = strlen(completion);
X pos += clen;
X space_available -= clen;
X if (space_available > 0)
X {
X //
X // We\'ve got the space to hold the completed filename.
X //
X (void)strcat(response, completion);
X (void)fputs(completion, stdout);
X }
X else
X {
X //
X // Allocate more space for response and adjust
X // message line.
X //
X char *nresponse = new char[pos + columns()/2 + 1];
X (void)strcpy(nresponse, response);
X (void)strcat(nresponse, completion);
X DELETE response;
X response = nresponse;
X clear_message_line();
X (void)fputs(&response[pos - columns()/2 + 1], stdout);
X space_available = columns()/2 + 1;
X }
X synch_display();
X break;
X
X#endif /*COMPLETION*/
X
X case KEY_CR: // we have the complete response
X response[pos] = 0;
X clear_message_line();
X synch_display();
X return response;
X
X case KEY_ABORT: // abort -- reset cursor to previous position
X move_cursor(dir_stack->top()->savedYPos(),
X dir_stack->top()->savedXPos());
X message("Aborted");
X return 0;
X
X case KEY_BKSP: // back up one character
X if (pos == 0)
X {
X ding();
X break;
X }
X backspace();
X DC ? delete_char_at_cursor() : clear_to_end_of_line();
X --pos;
X ++space_available;
X if (space_available == columns())
X {
X //
X // The only way this can happen is if we
X // had previously shifted the response to the left.
X // Now we must shift the response to the right.
X //
X clear_message_line();
X response[pos] = 0;
X if (pos + len < columns())
X {
X //
X // Output message and response-to-date.
X //
X (void)fputs(nmsg, stdout);
X (void)fputs(response, stdout);
X space_available = columns() - pos - len;
X }
X else if (pos < columns())
X {
X //
X // Display the response.
X //
X (void)fputs(response, stdout);
X space_available = columns() - strlen(response);
X } else
X {
X //
X // Display the backend of the response
X //
X (void)fputs(&response[pos - columns()/2 + 1], stdout);
X space_available = columns()/2 + 1;
X }
X }
X synch_display();
X break;
X
X default: ding(); break; // ignore other characters
X }
X }
X}
X
X/*
X** lines_displayed - returns the number of lines in the DirList
X** currently displayed on the screen.
X*/
X
Xint lines_displayed(DirList *dl)
X{
X DirLine *ln = dl->firstLine();
X for (int i = 1; ln != dl->lastLine(); i++, ln = ln->next()) ;
X return i;
X}
X
X/*
X** message - prints a message on the last line of the screen.
X** It is up to the calling process to put the cursor
X** back where it belongs. Synchs the display. It can
X** be called as either:
X**
X** message\(msg\);
X** or
X** message\(fmt, str\);
X**
X** In the later case it must be the case that the format `fmt\'
X** has exactly one `%\' into which the `str\' will be substituted
X** as in the ?printf\(\) functions.
X*/
X
Xstatic char message_window_dirty = 0;
X
Xvoid message(const char *fmt, const char *str)
X{
X char *msg; // the complete message to be output
X int allocated = 0; // was `msg\' allocated in new\(\) space?
X
X clear_message_line();
X
X if (str)
X {
X msg = new char[strlen(fmt) + strlen(str) + 1];
X const char *token = strchr(fmt, '%');
X if (token == 0)
X //
X // This shouldn\'t happen. But if it does, let\'s
X // just print the format `fmt\'.
X //
X msg = (char *)fmt;
X else
X {
X (void)strncpy(msg, fmt, token - fmt);
X msg[token - fmt] = 0; // strncpy doesn\'t nullify the string
X (void)strcat(msg, str);
X (void)strcat(msg, token + 1);
X allocated = 1;
X }
X }
X else
X msg = (char *)fmt;
X
X if (strlen(msg) < columns())
X (void)fputs(msg, stdout);
X else
X (void)printf("%*.*s", columns() - 1, columns() - 1, msg);
X
X synch_display();
X message_window_dirty = 1;
X if (allocated) DELETE msg;
X}
X
X/*
X** get_key - reads a key using getch\(\) and then clears the message window,
X** if it needs to be cleared. Used only by read_commands in the
X** main switch statement so that message\(\) doesn\'t need to sleep\(\)
X** and clear\(\) on the messages that get written. This way, the
X** message window is cleared after each keypress within the main
X** loop, when necessary. We also check for and deal with window
X** size changes and the UP and DOWN arrow keys here.
X*/
X
Xstruct arrow_key {
X int len;
X int *seq;
X arrow_key(const char *);
X};
X
Xarrow_key::arrow_key(const char *str)
X{
X if (str == 0)
X {
X //
X // The capability isn\'t defined.
X //
X len = 0;
X seq = 0;
X return;
X }
X
X seq = new int[12]; // should be ample
X
X int i = 0;
X do
X {
X switch (*str)
X {
X case '\\':
X {
X int c = *++str;
X switch (c)
X {
X case 'E': seq[i++] = 0x1b; break;
X case 'b': seq[i++] = '\b'; break;
X case 'f': seq[i++] = '\f'; break;
X case 'n': seq[i++] = '\n'; break;
X case 'r': seq[i++] = '\r'; break;
X case 't': seq[i++] = '\t'; break;
X case 'v': seq[i++] = '\v'; break;
X case '\\': seq[i++] = '\\'; break;
X case '\'': seq[i++] = '\''; break;
X case '\"': seq[i++] = '\"'; break;
X case '^': seq[i++] = '^'; break;
X default:
X error("invalid escape in /etc/termcap for arrow key: \\%c",
X c);
X break;
X }
X break;
X }
X case '^':
X {
X int c = *++str;
X if (isalpha(c))
X {
X seq[i] = (c > 'a' ? c - 'a' : c - 'A') + 1;
X i++; // g++ 2.2.2 chokes if I write seq\[i++\] = ...
X }
X else
X switch(c)
X {
X case '[': seq[i++] = 0x1b; break;
X case '\\': seq[i++] = 0x1c; break;
X case ']': seq[i++] = 0x1d; break;
X case '^': seq[i++] = 0x1e; break;
X case '_': seq[i++] = 0x1f; break;
X default:
X error("invalid control sequence for arrow key: ^%c",
X c);
X break;
X }
X }
X break;
X default:
X seq[i++] = *str;
X break;
X }
X } while (*++str);
X
X len = i;
X}
X
Xint get_key(DirList *dl)
X{
X int key, index = 0;
X static arrow_key up(KU), down(KD);
X static int *keys = 0;
X static int remaining = 0;
X
X if (keys == 0) keys = new int[max(up.len, down.len)];
X
X#ifdef SIGWINCH
X if (win_size_changed)
X {
X win_size_changed = 0;
X adjust_window();
X redisplay();
X }
X#endif
X
X if (remaining)
X {
X //
X // We have some characters left over from a partial match
X // of an arrow key; use them up.
X //
X key = keys[0];
X remaining--;
X for (int i = 0; i < remaining; i++) keys[i] = keys[i+1];
X return key;
X }
X else
X key = read_from_keybd();
X
X if (message_window_dirty)
X {
X clear_message_line();
X move_cursor(dl->savedYPos(), dl->savedXPos());
X synch_display();
X message_window_dirty = 0;
X }
X
X //
X // Now deal with potential arrow keys.
X //
X if (KU || KD)
X {
X for (index = 0; (index < up.len && up.seq[index] == key) ||
X (index < down.len && down.seq[index] == key); index++)
X {
X if ((up.len - 1) == index && up.seq[index] == key)
X return KEY_ARROW_UP;
X if ((down.len - 1) == index && down.seq[index] == key)
X return KEY_ARROW_DOWN;
X if (index == (max(up.len, down.len) - 1)) break;
X keys[index] = key;
X key = read_from_keybd();
X }
X if (index == 0)
X return key; // no initial match -- the most usual case
X else
X {
X //
X // We had a partial match, but not a complete one.
X // We must return the characters which we\'ve read in
X // the proper order so that the main command loop can
X // check for matches. The problem here is the potential
X // ambiguity between what the terminal claims to be arrow
X // keys and what has been hardcoded as the commands for
X // dired.
X //
X keys[index] = key;
X key = keys[0]; // what we\'ll return to the command loop
X for (int i = 0; i < index; i++) keys[i] = keys[i+1];
X remaining = index;
X return key;
X }
X }
X else
X return key;
X}
X
X/*
X** cleanup - cleanup and exit after a SIGHUP, SIGTERM, SIGQUIT or SIGINT
X*/
X
Xvoid cleanup(int) { term_display(); exit(0); }
X
X/*
X** initial_listing - prints the initial listing screen. Called by dired\(\)
X** and read_commands\(\) when rereading the current directory.
X** Adjusts firstLine\(\), lastLine\(\) and currLine\(\).
X*/
X
Xvoid initial_listing(DirList *dl)
X{
X static int first = 1;
X
X DirLine *ln = dl->head();
X dl->setFirst(ln);
X dl->setCurrLine(ln);
X cursor_home();
X for (int i = 0; i < rows()-2 && ln; ln = ln->next(), i++)
X {
X if (!first) clear_to_end_of_line();
X display_string(ln->line(), ln->length());
X }
X
X ln ? dl->setLast(ln->prev()) : dl->setLast(dl->tail());
X
X //
X // Don\'t forget to clear any remaining lines in those
X // cases when the screen hasn\'t already been cleared and
X // there are potentially dirty lines from a previous dired\(\).
X //
X if (!first)
X {
X move_cursor(i, 0);
X for (; i < rows() - 2; i++)
X {
X clear_to_end_of_line();
X cursor_down();
X }
X }
X if (first) first = 0;
X}
X
X#ifdef SIGWINCH
X
X/*
X** winch - set flag indicating window size changed.
X*/
X
Xvoid winch(int)
X{
X (void)signal(SIGWINCH, SIG_IGN);
X win_size_changed = 1;
X (void)signal(SIGWINCH, winch);
X}
X
X#endif
X
X/*
X** set_signals - set up our signal handlers
X*/
X
Xvoid set_signals()
X{
X (void)signal(SIGHUP, cleanup);
X (void)signal(SIGINT, cleanup);
X (void)signal(SIGQUIT, cleanup);
X (void)signal(SIGTERM, cleanup);
X#ifdef SIGTSTP
X (void)signal(SIGTSTP, termstop);
X#endif
X#ifdef SIGWINCH
X (void)signal(SIGWINCH, winch);
X#endif
X}
X
X/*
X** unset_signals - set signals back to defaults
X*/
X
Xvoid unset_signals()
X{
X (void)signal(SIGHUP, SIG_DFL);
X (void)signal(SIGINT, SIG_DFL);
X (void)signal(SIGQUIT, SIG_DFL);
X (void)signal(SIGTERM, SIG_DFL);
X#ifdef SIGTSTP
X (void)signal(SIGTSTP, SIG_DFL);
X#endif
X#ifdef SIGWINCH
X (void)signal(SIGWINCH, SIG_DFL);
X#endif
X}
X
X/*
X** leftshift_current_line - shifts the current line in DirList left until
X** its tail is visible.
X*/
Xvoid leftshift_current_line(DirList *dl)
X{
X int inc = dl->currLine()->length()-columns()+1;
X move_cursor(dl->savedYPos(), 0);
X clear_to_end_of_line();
X display_string(&(dl->currLine()->line())[inc],columns()-1);
X dl->saveYXPos(dl->savedYPos(), max(goal_column(dl)-inc, 0));
X move_cursor(dl->savedYPos(), dl->savedXPos());
X}
X
X/*
X** rightshift_current_line - rightshifts current line to "natural" position.
X*/
Xvoid rightshift_current_line(DirList *dl)
X{
X move_cursor(dl->savedYPos(), 0);
X clear_to_end_of_line();
X display_string(dl->currLine()->line(), dl->currLine()->length());
X dl->saveYXPos(dl->savedYPos(), goal_column(dl));
X move_cursor(dl->savedYPos(), dl->savedXPos());
X}
X
X#ifdef NO_STRSTR
X/*
X** strstr - from Henry Spencers ANSI C library suite
X*/
X
Xchar *strstr(const char *s, const char *wanted)
X{
X register const char *scan;
X register size_t len;
X register char firstc;
X
X //
X // The odd placement of the two tests is so "" is findable.
X // Also, we inline the first char for speed.
X // The ++ on scan has been moved down for optimization.
X //
X firstc = *wanted;
X len = strlen(wanted);
X for (scan = s; *scan != firstc || strncmp(scan, wanted, len) != 0; )
X if (*scan++ == '\0') return NULL;
X return (char *)scan;
X}
X
X#endif /* NO_STRSTR */
X
X#ifdef NO_STRCHR
X/*
X** strchr - find first occurrence of a character in a string. From Henry
X** Spencer\'s string\(3\) implementation.
X*/
X
Xchar *strchr(const char *s, char charwanted)
X{
X register const char *scan;
X
X //
X // The odd placement of the two tests is so NULL is findable.
X //
X for (scan = s; *scan != charwanted;) /* ++ moved down for opt. */
X if (*scan++ == '\0') return 0;
X return (char *)scan;
X}
X
X/*
X** strrchr - find last occurrence of a character in a string. From Henry
X** Spencer\'s string\(3\) implementation.
X*/
X
Xchar *strrchr(const char *s, char charwanted)
X{
X register const char *scan;
X register const char *place;
X
X place = NULL;
X for (scan = s; *scan != '\0'; scan++)
X if (*scan == charwanted) place = scan;
X if (charwanted == '\0') return (char *)scan;
X return (char *)place;
X}
X
X#endif /* NO_STRCHR */
END_OF_FILE
if test 43137 -ne `wc -c <'utilities.C'`; then
echo shar: \"'utilities.C'\" unpacked with wrong size!
fi
# end of 'utilities.C'
fi
echo shar: End of archive 5 \(of 5\).
cp /dev/null ark5isdone
MISSING=""
for I in 1 2 3 4 5 ; do
if test ! -f ark${I}isdone ; then
MISSING="${MISSING} ${I}"
fi
done
if test "${MISSING}" = "" ; then
echo You have unpacked all 5 archives.
rm -f ark[1-9]isdone
else
echo You still need to unpack the following archives:
echo " " ${MISSING}
fi
## End of shell archive.
exit 0
exit 0 # Just in case...
--
Andrew Patrick acting as Comp.Sources.Reviewed Moderator
Department of Communications, Ottawa, CANADA
csr@calvin.dgbt.doc.CA
Technical Details
Message-ID:
<1992Oct28.024117.28388@rick.dgbt.doc.ca>Path:
arc1-ca.pugleaf.net!sparky!uunet!utcsri!torn!cunews!dgbt!rick.dgbt.doc.ca!calvin.dgbt.doc.ca!csrShow Full Headers
Complete Article Headers
Newsgroups: comp.sources.reviewed Path: arc1-ca.pugleaf.net!sparky!uunet!utcsri!torn!cunews!dgbt!rick.dgbt.doc.ca!calvin.dgbt.doc.ca!csr From: Mike Lijewski <lijewski@theory.tc.cornell.edu> Subject: v02i037: dired - (Ver. 1.8) directory editor in C++, Part05/05 Message-ID: <1992Oct28.024117.28388@rick.dgbt.doc.ca> Originator: csr@calvin.dgbt.doc.ca Sender: news@rick.dgbt.doc.ca Nntp-Posting-Host: calvin.dgbt.doc.ca Organization: Communications Research Centre, Ottawa Date: Wed, 28 Oct 1992 02:41:17 GMT Approved: csr@calvin.dgbt.doc.ca Lines: 1606