C Programming

Al's project continues to grow with a general purpose text editor that (somehow) leads to a discussion of maintaining comments in code.


November 01, 1988
URL:http://www.drdobbs.com/cpp/c-programming/184408029

NOV88: C PROGRAMMING

This month adds a text editor to the PC C toolset that we are building. The past two "C Programming" columns contained a window library, data entry windows, and a menu manager. Future columns will add help windows, communications functions, and file transfer protocols to the package. Eventually it will all be gathered together into an integrated utility program. But before we get to this month's text editor, I want to deal with a reader concern which has been steadily gaining in volume.

Several readers have expressed their dismay that this project is locked in Turbo C, therefore locking them out. I recognize this as a valid concern and will address this in the January 1989 column.

The correction will take the form of a library of functions and macros that translate the Turbo C specific code into code acceptable to Microsoft's C. In the January column I will describe the library so that users of compilers can develop similar libraries. Now back to business.

To design a text editor, we need to examine our requirements, first to see why we need one and then to see what it should do. Probably nothing in computing is as personal as your editor, whether you use it for word processing or for entering source code. Because you and I are the users of this package, and because each of us already has a preferred editor and word processor, we should question our motives for adding yet another one to the selection. Then, assuming we find justification for another editor, its design must cater to the particular requirements of each of us. Not an easy objective, but a noble one, nonetheless.

The program that results from all this will be a communications utility package for access to an on-line service. On-line services involve the exchange of text--mail, forum conversations, and messages to SYSOPs--between the users. Therefore, an access program needs the entry of free-form text, in other words, a text editor. We could specify that a user can use his or her own editor, and we could provide a gateway to it in our code. Perhaps that feature is an additional requirement for our system. Even so, no doubt there will be other places in the program where text entry is required, places where it would be awkward to exit to a text editor and attach the resulting text data entry to the data structures being built. Real-time conference conversation processing might be one such application. It seems, therefore, that we need a window-oriented text editor, one that can be called from a program to collect text through a window into a buffer.

Because editors usually operate from a command language, and because most users have personal preferences for editing commands, our program must also allow the user to modify the command set.

Listing One and Listing Two are editor.h and editor.c. These constitute the basic window text editor. Next month we will add a pop-up menu shell, text searching functions, and some file management features to create, edit, save, and merge text files. The expanded editor program will constitute a tiny word processor, one that can be integrated into a C application exactly the way we intend to use it for mail and forum messages. As with all our tools, the editor is designed to be reusable for other projects that you or I might undertake.

Before I describe the code, let's discuss the editor philosophy. I had to consider three things in the design: the way the editor will work internally; how it can be customized; and what word-processing features it will include.

Internal Text Formats and Operation

I decided to adapt the window text editor from my books about Turbo C and QuickC. The code works properly, is mine and yours to use, and is reasonably small. It is modified here to use the more concise window functions from this column and to be extensible in that it is easy to add commands without modifying the source code of the editor itself. It also now has the potential for recursive calls to the editor. Although there are no immediate plans for a multiple-window editor or for one that can be called recursively, the editor should not preclude those features. Therefore, the code will allow an application to preserve the editor's current environment and reinvoke the editor for a different environment (window, buffer, and so forth).

To use the window text editor in a program, you must provide a window and a buffer with, perhaps, some text already in it. The buffer's size is a function of the width of the widest allowable text line and the maximum number of lines of text. You tell the editor these two dimensions when you execute it. The editor assumes that the window is wide enough to hold the lines and that the buffer is deep enough for a text file with the maximum number of lines. This approach, which uses trailing spaces to fill out each line, uses more buffer memory than one that uses newlines in the text, but the code for managing the cursor and blocks of text is less complex. The buffer is essentially an xy rectangle of y rows by x columns.

Because the fixed rectangular buffer must fit horizontally inside the window, the editor does not provide for horizontal scrolling. This means that lines cannot extend beyond the right margin of the window. It also means that a text file cannot have variable margins. These are features that we trade off to get an efficient editor within an application, and their loss does not compromise our requirements for a message composition tool.

The editor does not record or otherwise use the tab character (\ t) in a text buffer. When you type the Tab key the editor inserts the appropriate number of spaces to put the cursor at the next tab stop. Tab stops are specified as fixed intervals of character positions. The width of the interval can be configured when you compile the editor code.

The editor allows you to mark text blocks for move, copy, delete, and paragraph operations. These blocks are online boundaries rather than at character positions. This choice, too, was made in the interest of efficient code.

Customizing the Editor

Customizing the window editor consists of changing some default values and providing your own command keys instead of the ones that are published. If you make such changes, when the menu shell and help windows are added later you will need to change the text that tells you what these commands are. For now, however, you only need to redefine the configured command values found in editor.h. The editor commands are single key stroke values that are delivered by the function getkey in window.c (September DDJ). The configurable commands have mnemonic global names such as END_LINE and DELETE_WORD. Change their values by defining different values for them. Note that this approach uses single keystrokes as editor commands. If you want to make this editor emulate the WordStar double stroke sequences like the Borland and QuickC editors do, you must write a getkey substitution that knows about them. There is a precedent for doing this--lots of people are comfortable with the WordStar command set--but the editor as published here does not support double stroke sequences.

Three other configuration items are found in editor.h. These are the width of the tab stops, whether the editor comes up in insert or overwrite mode, and whether the editor comes up with automatic paragraph reforming enabled.

To change the editor's window colors, change the TEXTFG, TEXTBG, BLOCKFG, and BLOCKBG global values in window.h from the September "C Programming" column. Text colors are the normal colors for the text editor window and should be specified as the window's colors when you establish the window that the editor will use. Block colors are set by the editor when a block is marked. The editor resets the window to the text colors for nonblocked displays. Color configuration was discussed in September.

Of course, one other level of customization is available to you. You have the source code to this editor, and you can make it do anything you want within the limits of your abilities with the C language.

Word Processing Features

No one will use this editor to replace Word, WordPerfect, XyWrite, or WordStar; nor will you toss out Brief, vi, Vedit, EMACS, or the Norton Editor. Nonetheless, the editor needs a sufficient set of word processing features to make it useable for entering messages. From my experience with word processors and on-line services comes a set of features that I believe to be the minimum, and these are built into the editor. Realizing, however, that the need for more features will no doubt come later, I wrote the code so that extensions--those new, undiscovered features--could be easily tacked on. More about that later; first the minimum features.

Text Entry--You can type words into the buffer. Word wrap occurs when the word being typed reaches the right margin or when an inserted character pushes the rightmost word to the margin. When you type, the insert or overstrike mode is effective. Paragraphs can be automatically reformatted as you type or not as determined by a programmed toggle.

Cursor Movement-You can move the cursor around by characters words pages, tabs, to the right or left of the line, to the top or bottom of the window, or to the beginning or end of the text. If you type the Enter key, the cursor moves to the next line, first column. If this is done in insert mode, the line is split.

Editing--You can delete characters (left or right), words, lines, or blocks. You can mark a block of lines and then move, copy, delete, or form a paragraph from the block. You can unmark a marked block.

Paragraphs--A paragraph begins with an indented line. The indent is the number of spaces in the tab interval. Automatic paragraph reforming occurs from the cursor position up to the next paragraph or blank line. The paragraph command does likewise when no block has been marked.

These are the basic text editing features of a buffer of text that is edited through a window by our text editor. The file management aspects of the editor project come next month when we see how to read and write text files into and from the buffer. We will also add the text searching algorithms and a menu shell. Nothing about this program pushes the technology. We have added a tool to our collection, one that will enable free-format text to be entered and changed in a window by the users of our programs.

The Editor Software

Listing One is editor.h. It is used to define the editor's command set and default mode Settings. The #define statements for the commands assign key values to command mnemonics. The key values are taken from window.h or are defined here. Keystrokes have the values returned by the getkey function in window.c. The values for function keys are formed by adding the value 128 to the scan code returned by BIOS thus setting the most significant bit and forming a unique 8-bit value for the key stroke. Because window.h does not have all the possible key values defined, each addition to the tool set that needs other key values must define them themselves. Thus, we have the two Alt key definitions at the top of editor.h.

The TAB mnemonic defines the width of tab stops. As published here, TAB is 4, so tabs will occur at 5, 9,13, and so on. If you change TAB, the tab positions will change accordingly.

The REFORMING variable is set to TRUE or FALSE to specify whether automatic paragraph reforming will occur as you type. If you set it to TRUE, the editor tests to see if the paragraph needs to be reformed each time a character or word is deleted and when word wrap occurs. This test compares the white space at the end of the current line with the length of the first word on the next line. If the word will fit at the end of the current line, the paragraph is reformed. This is a convenience when you are entering raw text; it would be a pain in the neck for code or a table. Therefore, the menu software next month will include a command to turn the mode on and off. On some slow processors the automatic reformat gets sluggish when a word is wrapped near the top of a long paragraph, and the display of the keys you type falls behind the speed of a medium to fast typist. To improve this performance, remove the call to test-para in the function carttn in editor.c. This will change the reformat rules during word wrap to work on just the current and next line of text, pushing the rest of the paragraph down a full line when room is needed. Later a press of the PARAGRAPH command (F2) will complete the reformat operation. This approach is close to the way WordStar works. The original approach is similar to, but not as fast as, XyWrite.

The INSERT variable is set to TRUE or FALSE to indicate whether typing is in insert or overstrike mode. The value assigned to the global symbol is the default mode when the editor is started. Thereafter the Ins key toggles the mode. The #ifndef is for programs that use the data entry functions from October. That library also has an INSERT mode for data entry templates, and the #ifndef prevents the two #define statements from colliding.

editor.h defines the edit_env structure, which contains all the variables related to the environment of a particular invocation of the editor. Later, if we decide to use multiple windows or if we need to invoke a secondary editor from within a primary one, we will stack the incidence of the structure declared as ev in editor.c.

editor.c contains the window text editor function. You call the function named text_editor and pass it the address of your edit buffer, the maximum number of lines in the buffer, and the length of the longest line. You must have established a window with estabtish_window in window.c, and that window must be able to contain lines of the width specified in the call to text_editor. In other words, the window must be at least as wide as the line length plus two for the window borders. The size of the buffer must be at least the line length times the line count and should contain displayable text data or spaces. The text_editor function returns the keystroke that terminated the edit session. This value will be either the Esc key or the QUIT command (Alt-Q as published). A program can test this value to know what the user intends to do with the buffer of text.

The text_editor function displays the text in the window and begins accepting data keys or commands from the user. With each keystroke the function pointed to by the status_line function pointer is called. This allows the application program to show buffer status information such as the page and line numbers and the mode settings. To use this feature, the application must initialize the pointer to the address of the function that displays the status. We will use this feature next month.

The variable named forcechar appears on lines 84 and 85. If forcechar has a non-zero value, that value is substituted for the next key press. This mechanism allows external code to force the execution of a command. External code can be defined by an address in the function pointer named editfunc. If you initialize this pointer to the address of a function, the function will be called whenever the editor cannot recognize a keystroke. The value of the keystroke is passed in the call to the function on line 244. The function can view the external structure named ev to examine the editor's environment, it can modify that environment, and it can force execution of a command when it returns by placing a command value in forcechar This mechanism will be used next month to add file management, menus, and text searching to the editor. It is how we make the editor extensible without modifying the code in the editor itself.

Crotchet Number Six: Obsolete Comments

Most of the code in editor.c explains itself about as well as C code can explain itself without extensive comments. At least I think so. It works for me and I hope it does for you too. My commenting practices follow a convention that identifies the purpose of each function in a comment at the beginning of the function. Variables that are not obvious get comments that describe their purpose. Where code gets downright abstruse, I will insert comments as it goes along, but mostly I prefer to let the code describe itself. This habit and preference comes from years of reading the code of others where their extensive and verbose comments predate the code by generations of modifications. I have been lulled into believing beautifully crafted comments and have thus been subliminally conditioned to assume things that are not true. This loses time. Later, when my confusion reaches an intolerable level, I resort to reading the code, only to find that it disagrees with the comments. No matter whether the comments are statements of intent never realized or accurate descriptions of code no longer in place; the comments say one thing and the code does another.

As a matter of conviction and to preserve the remnants of my sanity, I now refuse to read comments that are written as pseudocode unless I am sure the program has never been modified. Even then I am reluctant. Those comments are rarely (if ever) maintained as the code is being developed or later when it is changed. Perhaps your experiences are different; perhaps the rigidly enforced standards and procedures of your employer keep this crotchet out of your shop; and perhaps you believe that. I do not.

C language code is not always its own best documentation. It is, however, the most reliable statement of what is going on in the program. My practice serves me well because I tend to remember what those cryptic variable names mean, and I prefer small functions with simple purposes. If the function works, its purpose is understood, and it is small, then it can be thought of as a black box, can be read, and can be trusted. Not that I always practice what I preach; text_editor is a big function, although it is mostly a lot of small cases to a key stroke switch.

By the way--I don't much care to read code that has been commented out either. A faraway, out-of-sight #if DEBUG statement or an as yet unterminated /* start-of-comment token can have you reading reams of code that does not exist.

On the other hand, my pal Bill Chaney says that anyone who fails to provide ample comments in an assembly language program should be required for penance to spend a year maintaining COBOL/CICS screen driver programs for the Puerto Rican income tax system.

An Example for Using the Text Editor

Testedit.c, Listing Three is a simple example of the use of the text editor. Compile and link testedit.c with editor.c and window.c. You run it by naming a text file on the command line. This is not a standard text file; it is an image of the editor's rectangular buffer, so the first time you run testedit, you can give a file name that does not exist and the program will build it. testedit establishes a window, reads the file--if it exists--into the buffer, and calls the text_editor function. If the Esc key is not returned, which means that you pressed the QUIT key (Alt-Q), the buffer is written to the file that you named on the command line. If you press Esc, the program exits without writing the buffer.

This program is not very smart. Its purpose is to demonstrate how to set up, use, and exit from the window text editor. Next month's offering incorporates this new editor engine into the tiny word processor I mentioned earlier. I will take drastic measures to test the tiny word processor when I write next month's column. I will, temporarily at least, abandon my beloved XyWrite and use the tiny word processor for the month's work, risking the fruits of my creative labors to a new and unproven text editor. This, dear readers, is dedication and commitment. Expect nothing less from a DDJ columnist.

_C PROGRAMMING_ by Al Stevens

[LISTING ONE]




/* ------------ editor.h ------------- */
#define ALT_Q 144
#define ALT_R 147
/* -------- configured editor commands ---------- */
#define BACKTAB         SHIFT_HT
#define NEXTWORD        CTRL_FWD
#define PREVWORD        CTRL_BS
#define TOPSCREEN       CTRL_T
#define BOTSCREEN       CTRL_B
#define BEGIN_BUFFER    CTRL_HOME
#define END_BUFFER      CTRL_END
#define BEGIN_LINE      HOME
#define END_LINE        END
#define DELETE_LINE     ALT_D
#define DELETE_WORD     CTRL_D
#define INSERT          INS
#define QUIT            ALT_Q
#define PARAGRAPH       F2
#define BEGIN_BLOCK     F5
#define END_BLOCK       F6
#define MOVE_BLOCK      F3
#define COPY_BLOCK      F4
#define DELETE_BLOCK    F8
#define HIDE_BLOCK      F9
#define REPAINT         ALT_R
/* ------- configured default modes ----------- */
#define TAB 4
#define REFORMING TRUE    /* auto paragraph reformat mode */
#ifndef INSERTING
#define INSERTING TRUE    /* insert/overwrite mode        */
#endif
/* ---------- editor prototype ---------------- */
int text_editor(char *, int, int);
/* ------- macros ------------ */
#define curr(x,y) (ev.bfptr+(y)*ev.wwd+(x))
#define lineno(y) ((unsigned)(ev.bfptr-ev.topptr)/ev.wwd+(y))
/* ---------- editor environment ------------- */
struct edit_env    {
    int envinuse;       /* TRUE if the env is in use  */
    struct wn *wdo;     /* the editor window          */
    int wwd;            /* width of edit window       */
    int wsz;            /* size (chars) of window     */
    char *topptr;       /* -> first char in buffer    */
    char *bfptr;        /* -> first char in window    */
    char *nowptr;       /* -> current char in buffer  */
    char *lstptr;       /* -> last nonblank char      */
    char *endptr;       /* -> last char in buffer     */
    int text_changed;   /* TRUE if text has changed   */
    int nolines;        /* no. of lines in buffer     */
    int blkbeg;         /* marked block: 1st line     */
    int blkend;         /* marked block: last line    */
    int curr_x, curr_y; /* current buffer coordinates */
    int edinsert;       /* toggled insert mode        */
    int reforming;      /* toggled para reform mode   */
};




[LISTING TWO]


/* ----------------------- editor.c ---------------------- */

#include <stdio.h>
#include <ctype.h>
#include <conio.h>
#include <stdlib.h>
#include <string.h>
#include <mem.h>
#include <ctype.h>
#include "window.h"
#include "editor.h"

#define NEXTTAB (TAB-(ev.curr_x%TAB))
#define LASTTAB ((ev.wwd/TAB)*TAB)
#define PREVTAB (((ev.curr_x-1)%TAB)+1)

struct edit_env ev;         /* the editor environment      */
int do_display_text = TRUE; /* turns display on/off        */
extern struct wn wkw;       /* the current window          */
int forcechar;              /* externally force a kb char  */
void (*status_line)(void);  /* called once each keystroke  */
void (*editfunc)(int);      /* for unknown keystrokes      */

/* ---------- local function prototypes ----------- */
static int lastword(void);
static void last_char(void);
static void test_para(int);
static int trailing_spaces(int);
static int first_wordlen(int);
static int last_wordlen(void);
static void paraform(int);
static int blankline(int);
static void delete_word(void);
static void delete_line(void);
static void delete_block(void);
static void mvblock(int);
static void carrtn(int);
static void backspace(void);
static void fore_word(void);
static int spaceup(void);
static void back_word(void);
static int spacedn(void);
static void forward(void);
static int downward(void);
static void upward(void);
static void display_text(int);
static void disp_line(int y);
static void findlast(void);

/* ----- Process text entry for a window. ---- */
int text_editor(char *bf, int editlines, int editwidth)
{
    int depart, i, c;
    int svx, svlw, tx, tabctr, wraplen;

    current_window();
    depart = FALSE;
    tabctr = 0;
    if (ev.envinuse == FALSE)    {
        ev.wdo = &wkw;
        ev.wwd = editwidth;
        ev.wsz = ev.wwd * ev.wdo->ht;
        ev.topptr = ev.bfptr = bf;
        ev.nolines = editlines;
        ev.endptr = bf + ev.wwd * ev.nolines;
        ev.edinsert  = INSERTING;
        ev.reforming = REFORMING;
        ev.envinuse = TRUE;
    }
    set_cursor_type(ev.edinsert ? 0x0106 : 0x0607);
    display_text(0);
    findlast();
    /* ------- read text/command from the keyboard ------ */
    while (depart == FALSE)    {
        ev.nowptr = curr(ev.curr_x, ev.curr_y);
        if (status_line)
            (*status_line)(); /* external status line func */
        gotoxy(ev.curr_x + 2, ev.curr_y + 2);
        if (tabctr)    {          /* expand typed tabs */
            --tabctr;
            c = ' ';
        }
        else
            c = forcechar ? forcechar : getkey();
        forcechar = 0;
        switch (c)    {
/* ------------ fixed editor commands ----------------- */
            case '\r':
                carrtn(ev.edinsert);
                break;
            case UP:
                upward();
                break;
            case DN:
                downward();
                break;
            case FWD:
                forward();
                break;
            case '\b':
            case BS:
                if (!(ev.curr_x || ev.curr_y))
                    break;
                backspace();
                if (ev.curr_x == ev.wwd - 1)
                    last_char();
                if (c == BS)
                    break;
                ev.nowptr = curr(ev.curr_x, ev.curr_y);
            case DEL:
                movmem(ev.nowptr+1,ev.nowptr,
                    ev.wwd-1-ev.curr_x);
                *(ev.nowptr+ev.wwd-1-ev.curr_x) = ' ';
                disp_line(ev.curr_y);
                test_para(ev.curr_x+1);
                ev.text_changed = TRUE;
                break;
            case PGUP:
                ev.curr_y = 0;
                do_display_text = FALSE;
                for (i = 0; i < ev.wdo->ht; i++)
                    upward();
                do_display_text = TRUE;
                display_text(0);
                break;
            case PGDN:
                ev.curr_y = ev.wdo->ht-1;
                do_display_text = FALSE;
                for (i = 0; i < ev.wdo->ht; i++)
                    downward();
                do_display_text = TRUE;
                display_text(0);
                ev.curr_y = 0;
                break;
            case '\t':
                if (ev.curr_x + NEXTTAB < ev.wwd)    {
                    if (ev.edinsert)
                        tabctr = NEXTTAB;
                    else
                        ev.curr_x += NEXTTAB;
                }
                else
                    carrtn(ev.edinsert);
                break;
/* -------- configured editor commands --------------- */
            case REPAINT:
                display_text(ev.curr_y);
                break;
            case BACKTAB:
                if (ev.curr_x < TAB)    {
                    upward();
                    ev.curr_x = LASTTAB;
                }
                else
                    ev.curr_x -= PREVTAB;
                break;
            case NEXTWORD:
                fore_word();
                break;
            case PREVWORD:
                back_word();
                break;
            case BOTSCREEN:
                ev.curr_y = ev.wdo->ht - 1;
                break;
            case TOPSCREEN:
                ev.curr_y = 0;
                break;
            case BEGIN_BUFFER:
                ev.curr_x = ev.curr_y = 0;
                ev.bfptr = ev.topptr;
                display_text(0);
                break;
            case END_BUFFER:
                do_display_text = FALSE;
                ev.curr_x = 0;
                while (downward())
                    if (curr(0,ev.curr_y) >= ev.lstptr)
                        break;
                do_display_text = TRUE;
                display_text(0);
                break;
            case BEGIN_LINE:
                ev.curr_x = 0;
                break;
            case END_LINE:
                last_char();
                break;
            case DELETE_LINE:
                delete_line();
                ev.text_changed = TRUE;
                break;
            case DELETE_WORD:
                delete_word();
                ev.text_changed = TRUE;
                test_para(ev.curr_x);
                break;
            case INSERT:
                ev.edinsert ^= TRUE;
                set_cursor_type(ev.edinsert ? 0x106 : 0x607);
                break;
            case ESC:
            case QUIT:
                depart = TRUE;
                break;
            case PARAGRAPH:
                paraform(0);
                ev.text_changed = TRUE;
                break;
            case BEGIN_BLOCK:
                ev.blkbeg = lineno(ev.curr_y) + 1;
                if (ev.blkbeg > ev.blkend)
                    ev.blkend = ev.blkbeg;
                display_text(0);
                break;
            case END_BLOCK:
                ev.blkend = lineno(ev.curr_y) + 1;
                if (ev.blkend < ev.blkbeg)
                    ev.blkbeg = ev.blkend;
                display_text(0);
                break;
            case MOVE_BLOCK:
                mvblock(TRUE);
                ev.text_changed = TRUE;
                break;
            case COPY_BLOCK:
                mvblock(FALSE);
                ev.text_changed = TRUE;
                break;
            case DELETE_BLOCK:
                delete_block();
                ev.text_changed = TRUE;
                display_text(0);
                break;
            case HIDE_BLOCK:
                ev.blkbeg = ev.blkend = 0;
                display_text(0);
                break;
            default:
                if (!isprint(c))    {
                    /* ---- not recognized by editor --- */
                    if (editfunc)    {
                        /* --- extended commands --- */
                        (*editfunc)(c);
                        findlast();
                        display_text(0);
                    }
                    else
                        putch(BELL);
                    break;
                }
                /* --- displayable char: put in buffer --- */
                if (ev.nowptr == ev.endptr-1 ||
                   (lineno(ev.curr_y)+1 >=
                       ev.nolines && ev.edinsert &&
                       *curr(ev.wwd-2, ev.curr_y) != ' '))  {
                    error_message("End of buffer...");
                    break;
                }
                if (ev.edinsert) /* --- if insert mode --- */
                    movmem(ev.nowptr,ev.nowptr+1,
                        ev.wwd-1-ev.curr_x);
                if (ev.nowptr < ev.endptr)    {
                    if (ev.nowptr >= ev.lstptr)
                        ev.lstptr = ev.nowptr + 1;
                    *ev.nowptr = (char) c; /* put in buff */
                    disp_line(ev.curr_y);
                }
                if (ev.nowptr == curr(ev.wwd-1, ev.curr_y) &&
                    c == ' ' && ev.edinsert)    {
                    if (strncmp(curr(0,ev.curr_y+1),
                            "        ",TAB) == 0)    {
                        carrtn(TRUE);
                        break;
                    }
                }
                else if (ev.endptr &&
                        *curr(ev.wwd-1, ev.curr_y) != ' ')    {
                    /* ------- word wrap is needed ------- */
                    ev.nowptr = curr(ev.wwd-1, ev.curr_y);
                    svx = ev.curr_x;   /* save x vector */
                    svlw = lastword(); /* last word on line?*/
                    ev.curr_x = ev.wwd-1;
                    if (*(ev.nowptr-1) != ' ')
                        back_word();
                    tx = ev.curr_x;
                    wraplen = last_wordlen();
                    if (trailing_spaces(ev.curr_y+1) <
                            wraplen+2)
                        carrtn(TRUE);
                    else if (strncmp(curr(0,ev.curr_y+1),
                            "        ",TAB) == 0)
                        carrtn(TRUE);
                    else    {
                        ev.nowptr = curr(0, ev.curr_y+1);
                        movmem(ev.nowptr,ev.nowptr+wraplen+1,
                            ev.wwd-wraplen-1);
                        setmem(ev.nowptr, wraplen+1, ' ');
                        movmem(curr(ev.curr_x,ev.curr_y),
                            ev.nowptr,wraplen);
                        setmem(curr(ev.curr_x,ev.curr_y),
                            wraplen, ' ');
                        disp_line(ev.curr_y);
                        downward();
                        disp_line(ev.curr_y);
                    }
                    if (svlw)
                        ev.curr_x = svx-tx;
                    else
                        ev.curr_x = svx, --ev.curr_y;
                }
                forward();
                ev.text_changed = TRUE;
                break;
        }
    }
    return c;
}

/* ----- see if a word is the last word on the line ------ */
static int lastword()
{
    int x = ev.curr_x;
    char *bf = curr(ev.curr_x, ev.curr_y);
    while (x++ < ev.wwd-1)
        if (*bf++ == ' ')
            return 0;
    return 1;
}

/* --- go to last displayable character on the line --- */
static void last_char()
{
    char *bf = curr(0, ev.curr_y);
    ev.curr_x = ev.wwd-1;
    while (ev.curr_x && *(bf + ev.curr_x) == ' ')
        --ev.curr_x;
    if (ev.curr_x && ev.curr_x < ev.wwd - 1)
        ev.curr_x++;
}

/* ----- test to see if paragraph should be reformed ----- */
static void test_para(int x)
{
    int ts, fw;
    int svb, sve;

    if (ev.reforming && ev.curr_y < ev.nolines)    {
        ts = trailing_spaces(ev.curr_y);
        fw = first_wordlen(ev.curr_y+1);
        if (fw && ts > fw)    {
            svb = ev.blkbeg, sve = ev.blkend;
            ev.blkbeg = ev.blkend = 0;
            paraform(x);
            ev.blkbeg = svb, ev.blkend = sve;
            if (svb)
                display_text(0);
        }
    }
}

/* ---- count the trailing spaces on a line ----- */
static int trailing_spaces(int y)
{
    int x = ev.wwd-1, ct = 0;
    char *bf = curr(0, y);
    while (x >= 0)    {
        if (*(bf + x) != ' ')
            break;
        --x;
        ct++;
    }
    return ct;
}

/* ----- count the length of the first word on a line --- */
static int first_wordlen(int y)
{
    int ct = 0, x = 0;
    char *bf = curr(0, y);
    while (x < ev.wwd-1 && *bf == ' ')
        x++, bf++;
    while (x < ev.wwd-1 && *bf != ' ')
        ct++, x++, bf++;
    return ct;
}

/* ----- count the length of the last word on a line --- */
static int last_wordlen()
{
    int ct = 0, x = ev.wwd-1;
    char *bf = curr(x, ev.curr_y);
    while (x && *bf == ' ')
        --x, --bf;
    while (x && *bf != ' ')
        --x, --bf, ct++;
    return ct;
}

/* ------------ form a paragraph -------------- */
static void paraform(int x)
{
    char *cp1, *cp2, *cpend, *svcp;
    int x1, y1, firstline = TRUE;
    int y = ev.curr_y;

    if (!ev.blkbeg)    {    /* ---- if block not marked ---- */
        if (blankline(lineno(y)+1))
            return;        /* next line is blank, no reform */
        ev.blkbeg=ev.blkend=lineno(y)+1; /* pseudoblock */
        ev.blkend++;
        y1 = y+1;
        while (ev.blkend < ev.nolines)    { /* look for para */
            if (strncmp(curr(0, y1++), "        ", TAB) == 0)
                break;
            ev.blkend++;
        }
        --ev.blkend;
    }
    if (lineno(y) != ev.blkbeg-1)
        x = 0;
    x1 = x;
    cp1 = cp2 = ev.topptr + (ev.blkbeg - 1) * ev.wwd + x;
    cpend = ev.topptr + ev.blkend * ev.wwd;
    while (cp2 < cpend)    {
        while (*cp2 == ' ' && cp2 < cpend)    {
            if (firstline)
                *cp1++ = *cp2, x1++;
            cp2++;
        }
        firstline = FALSE;
        if (cp2 == cpend)
            break;
        /* ---- at a word ---- */
        while (*cp2 != ' ' && cp2 < cpend)    {
            if (x1 >= ev.wwd-1)    {
                /* wrap the word */
                svcp = cp1 + (ev.wwd - x1);
                while (*--cp1 != ' ')
                    *cp1 = ' ',    --cp2;
                x1 = 0;
                ev.blkbeg++;
                cp1 = svcp;
                if (y < ev.wdo->ht)
                    disp_line(y++);
            }
            *cp1++ = *cp2++;
            x1++;
        }
        if (cp2 < cpend)
            *cp1++ = ' ', x1++;
    }
    while (cp1 < cpend)
        *cp1++ = ' ';
     ev.blkbeg++;
    if (y < ev.wdo->ht)
        disp_line(y++);
    firstline = ev.blkbeg;
     if (ev.blkbeg <= ev.blkend)    {
        delete_block();
        display_text(y);
    }
    ev.blkbeg = ev.blkend = 0;
    if (firstline)
        display_text(0);
}

/* ------- test for a blank line ---------- */
static int blankline(int line)
{
    char *cp = ev.topptr + (line-1) * ev.wwd;
    int x = ev.wwd;
    while (x--)
        if (*cp++ != ' ')
            break;
    return !(x > -1);
}

/* ------------- delete a word -------------- */
static void delete_word()
{
    int wct = 0;
    char *cp1, *cp2;

    cp1 = cp2 = curr(ev.curr_x, ev.curr_y);
    if (*cp2 == ' ')
        while (*cp2 == ' ' && ev.curr_x + wct < ev.wwd)
            wct++, cp2++;
    else    {
        while (*cp2 != ' ' && ev.curr_x + wct < ev.wwd)
            wct++, cp2++;
        while (*cp2 == ' ' && ev.curr_x + wct < ev.wwd)
            wct++, cp2++;
    }
    movmem(cp2, cp1, ev.wwd - ev.curr_x - wct);
    setmem(cp1 + ev.wwd - ev.curr_x - wct, wct, ' ');
    disp_line(ev.curr_y);
}

/* ----------- delete a line --------------- */
static void delete_line()
{
    char *cp1, *cp2;
    int len;

    cp1 = ev.bfptr + ev.curr_y * ev.wwd;
    cp2 = cp1 + ev.wwd;
    if (cp1 < ev.lstptr)    {
        len = ev.endptr - cp2;
        movmem(cp2, cp1, len);
        ev.lstptr -= ev.wwd;
        setmem(ev.endptr - ev.wwd, ev.wwd, ' ');
        display_text(ev.curr_y);
    }
}

/* ----------- delete a block ------------- */
static void delete_block()
{
    char *cp1, *cp2;
    int len;

    if (!ev.blkbeg || !ev.blkend)    {
        error_message("No block marked ...");
        return;
    }
    cp1 = ev.topptr + ev.blkend * ev.wwd;
    cp2 = ev.topptr + (ev.blkbeg - 1) * ev.wwd;
    len = ev.endptr - cp1;
    movmem(cp1, cp2, len);
    setmem(cp2 + len, ev.endptr - (cp2 + len), ' ');
    ev.blkbeg = ev.blkend = 0;
    ev.lstptr -= cp1 - cp2;
}

/* ------- move and copy text blocks -------- */
static void mvblock(int moving)
{
    char *cp1, *cp2, *hd;
    unsigned len;

    if (!ev.blkbeg || !ev.blkend)    {
        error_message("No block marked ...");
        return;
    }
    if (lineno(ev.curr_y) >= ev.blkbeg-1
            && lineno(ev.curr_y) <= ev.blkend-1)    {
        error_message("Don't move/copy a block into itself");
        return;
    }
    len = (ev.blkend - ev.blkbeg + 1) * ev.wwd;
    if ((hd = malloc(len)) == NULL)
        return;
    cp1 = ev.topptr + (ev.blkbeg-1) * ev.wwd;
    movmem(cp1, hd, len);
    cp2 = ev.topptr + lineno(ev.curr_y) * ev.wwd;
    if (moving)    {
        if (lineno(ev.curr_y) > ev.blkbeg-1)
            cp2 -= len;
        delete_block();
    }
    if (cp2+len <= ev.endptr)    {
        movmem(cp2, cp2 + len, ev.endptr - cp2 - len);
        movmem(hd, cp2, len);
        ev.lstptr += cp1 - cp2;
    }
    else
        error_message("Not enough room...");
    free(hd);
    ev.blkbeg = ev.blkend = 0;
    display_text(0);
}

/* ------- find the last character in the buffer -------- */
static void findlast()
{
    char *lp = ev.endptr - 1, *tp = ev.topptr;
    while (lp > tp && *lp == ' ')
        --lp;
    if (*lp != ' ')
        lp++;
    ev.lstptr = lp;
}

/* -------- carriage return -------- */
static void carrtn(int insert)
{
    int insct;
    char *cp = curr(ev.curr_x, ev.curr_y);
    char *nl = cp+((cp-ev.topptr)%ev.wwd);
    int ctl = 2;
    if (lineno(ev.curr_y) + 2 < ev.nolines)
        if (insert && nl < ev.endptr)    {
            insct = ev.wwd - ev.curr_x;
            while (ctl--)    {
                if (ev.endptr > cp + insct)    {
                    movmem(cp, cp+insct, ev.endptr-insct-cp);
                    setmem(cp, insct, ' ');
                }
                else if (ctl == 1)
                    setmem(cp, ev.endptr - cp, ' ');
                cp += insct * 2;
                insct = ev.curr_x;
            }
        }
    ev.curr_x = 0;
    downward();
    if (insert)    {
        ev.text_changed = TRUE;
        test_para(0);
        display_text(ev.curr_y-1);
        if (lineno(ev.curr_y) + 2 < ev.nolines)
            if ((ev.lstptr + ev.wwd) <= ev.endptr)
                if (ev.lstptr > curr(ev.curr_x, ev.curr_y))
                    ev.lstptr += ev.wwd;
    }
}

/* ------- move the buffer offset back one position ------ */
static void backspace()
{
    if (ev.curr_x == 0)    {
        if (ev.curr_y)
            ev.curr_x = ev.wwd - 1;
        upward();
    }
    else
        --ev.curr_x;
}

/* -------- move the buffer offset forward one word ------ */
static void fore_word()
{
    while (*ev.nowptr != ' ')    {
        if (spaceup() == 0)
            return;
        if (ev.curr_x == 0)
            break;
    }
    while (*ev.nowptr == ' ')
        if (spaceup() == 0)
            return;
}

static int spaceup()
{
    if (ev.nowptr >= ev.lstptr)
        return 0;
    ev.nowptr++;
    forward();
    return 1;
}

/* ------- move the buffer offset backward one word ------ */
static void back_word()
{
    spacedn();
    while (*ev.nowptr == ' ')
        if (spacedn() == 0)
            return;
    while (*ev.nowptr != ' ')    {
        if (ev.curr_x == 0)
            return;
        if (spacedn() == 0)
            return;
    }
    spaceup();
}

static int spacedn()
{
    if (ev.nowptr == ev.topptr)
        return 0;
    --ev.nowptr;
    backspace();
    return 1;
}

/* ----- move the buffer offset forward one position ----- */
static void forward()
{
    int ww = ev.wwd;
    if (++ev.curr_x == ww)    {
        downward();
        ev.curr_x = 0;
    }
}

/* ------- move the buffer offset down one position ------ */
static int downward()
{
    if (ev.curr_y < ev.wdo->ht - 1)    {
        ev.curr_y++;
        return 1;
    }
    else if ((ev.bfptr + ev.wsz) < ev.endptr)    {
        ev.bfptr += ev.wwd;
        if (do_display_text)    {
            scroll_window(1);
            disp_line(ev.wdo->ht-1);
        }
        return 1;
    }
    return 0;
}

/* -------- move the buffer offset up one position ------ */
static void upward()
{
    if (ev.curr_y)
        --ev.curr_y;
    else if ((ev.topptr + ev.wwd) <= ev.bfptr)    {
        ev.bfptr -= ev.wwd;
        if (do_display_text)    {
            scroll_window(0);
            disp_line(0);
        }
    }
}

/* ---- display lines in a window ------ */
static void display_text(y)
{
    while (y < ev.wdo->ht)
        disp_line(y++);
}

/* ---------- Display a line -------- */
static void disp_line(int y)
{
    char ln[81];

    if (lineno(y) >= ev.blkbeg-1)
        if (lineno(y) <= ev.blkend-1)    {
            textcolor(BLOCKFG);
            textbackground(BLOCKBG);
        }
    movmem(ev.bfptr+y*ev.wwd, ln, ev.wwd);
    ln[ev.wwd] = '\0';
    writeline(2, y+2, ln);
    textcolor(TEXTFG);
    textbackground(TEXTBG);
}



[LISTING THREE]


/* --------- testedit.c ------------ */
#include <stdio.h>
#include <mem.h>
#include <conio.h>
#include "window.h"
#include "editor.h"

#define LNS 40               /* number of editor lines   */
#define WD  60               /* length of an editor line */
#define LF (1+(80-WD)/2)     /* leftmost column          */
#define TP (1+(25-LNS/2)/2)  /* top row                  */
#define RT LF+WD+1           /* rightmost column         */
#define BT TP+LNS/2+1        /* bottom row               */

char notes[LNS*WD];
void main(int, char **);

void main(int argc, char **argv)
{
    FILE *fd;
    if (argc > 1)    {
        setmem(notes, sizeof notes, ' ');
        if ((fd = fopen(argv[1], "r")) != NULL) {
            fread(notes, WD, LNS, fd);
            fclose(fd);
        }
        clear_screen();
        establish_window(LF,TP,RT,BT,TEXTFG,TEXTBG,FALSE);
        if (text_editor(notes, LNS, WD) != ESC)  {
            fd = fopen(argv[1], "w");
            fwrite(notes, WD, LNS, fd);
            fclose(fd);
        }
        delete_window();
        clear_screen();
        set_cursor_type(0x0607);
    }
}









Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.