🚀 go-pugleaf

RetroBBS NetNews Server

Inspired by RockSolid Light RIP Retro Guy

Article View: comp.sources.reviewed
Article #6

v02i037: dired - (Ver. 1.8) directory editor in C++, Part05/05

#6
From: Mike Lijewski
Date: Wed, 28 Oct 1992 02:41
1606 lines
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!csr

Show 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