1 /*******************************************************************************
2 *									       *
3 * textDisp.c - Display text from a text buffer				       *
4 *									       *
5 * Copyright (C) 1999 Mark Edel						       *
6 *									       *
7 * This is free software; you can redistribute it and/or modify it under the    *
8 * terms of the GNU General Public License as published by the Free Software    *
9 * Foundation; either version 2 of the License, or (at your option) any later   *
10 * version. In addition, you may distribute version of this program linked to   *
11 * Motif or Open Motif. See README for details.                                 *
12 * 									       *
13 * This software is distributed in the hope that it will be useful, but WITHOUT *
14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or        *
15 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License        *
16 * for more details.							       *
17 * 									       *
18 * You should have received a copy of the GNU General Public License along with *
19 * software; if not, write to the Free Software Foundation, Inc., 59 Temple     *
20 * Place, Suite 330, Boston, MA  02111-1307 USA		                       *
21 *									       *
22 * Nirvana Text Editor	    						       *
23 * June 15, 1995								       *
24 *									       *
25 * Written by Mark Edel							       *
26 *									       *
27 *******************************************************************************/
28 
29 #ifdef HAVE_CONFIG_H
30 #include "../config.h"
31 #endif
32 
33 #include "textDisp.h"
34 #include "textBuf.h"
35 #include "text.h"
36 #include "textP.h"
37 #include "nedit.h"
38 #include "calltips.h"
39 #include "highlight.h"
40 #include "rangeset.h"
41 #include "../util/nedit_malloc.h"
42 
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <limits.h>
47 #ifdef VMS
48 #include "../util/VMSparam.h"
49 #else
50 #ifndef __MVS__
51 #include <sys/param.h>
52 #endif
53 #endif /*VMS*/
54 
55 #include <Xm/Xm.h>
56 #include <Xm/ScrolledW.h>
57 #include <Xm/ScrollBar.h>
58 #include <Xm/Label.h>
59 #include <X11/Shell.h>
60 
61 #ifdef HAVE_DEBUG_H
62 #include "../debug.h"
63 #endif
64 
65 /* Masks for text drawing methods.  These are or'd together to form an
66    integer which describes what drawing calls to use to draw a string */
67 #define FILL_SHIFT 8
68 #define SECONDARY_SHIFT 9
69 #define PRIMARY_SHIFT 10
70 #define HIGHLIGHT_SHIFT 11
71 #define STYLE_LOOKUP_SHIFT 0
72 #define BACKLIGHT_SHIFT 12
73 
74 #define FILL_MASK (1 << FILL_SHIFT)
75 #define SECONDARY_MASK (1 << SECONDARY_SHIFT)
76 #define PRIMARY_MASK (1 << PRIMARY_SHIFT)
77 #define HIGHLIGHT_MASK (1 << HIGHLIGHT_SHIFT)
78 #define STYLE_LOOKUP_MASK (0xff << STYLE_LOOKUP_SHIFT)
79 #define BACKLIGHT_MASK  (0xff << BACKLIGHT_SHIFT)
80 
81 #define RANGESET_SHIFT (20)
82 #define RANGESET_MASK (0x3F << RANGESET_SHIFT)
83 
84 /* If you use both 32-Bit Style mask layout:
85    Bits +----------------+----------------+----------------+----------------+
86     hex |1F1E1D1C1B1A1918|1716151413121110| F E D C B A 9 8| 7 6 5 4 3 2 1 0|
87     dec |3130292827262524|2322212019181716|151413121110 9 8| 7 6 5 4 3 2 1 0|
88         +----------------+----------------+----------------+----------------+
89    Type |             r r| r r r r b b b b| b b b b H 1 2 F| s s s s s s s s|
90         +----------------+----------------+----------------+----------------+
91    where: s - style lookup value (8 bits)
92         F - fill (1 bit)
93         2 - secondary selection  (1 bit)
94         1 - primary selection (1 bit)
95         H - highlight (1 bit)
96         b - backlighting index (8 bits)
97         r - rangeset index (6 bits)
98    This leaves 6 "unused" bits */
99 
100 /* Maximum displayable line length (how many characters will fit across the
101    widest window).  This amount of memory is temporarily allocated from the
102    stack in the redisplayLine routine for drawing strings */
103 #define MAX_DISP_LINE_LEN 1000
104 
105 /* Macro for getting the TextPart from a textD */
106 #define TEXT_OF_TEXTD(t)    (((TextWidget)((t)->w))->text)
107 
108 enum positionTypes {CURSOR_POS, CHARACTER_POS};
109 
110 static void updateLineStarts(textDisp *textD, int pos, int charsInserted,
111         int charsDeleted, int linesInserted, int linesDeleted, int *scrolled);
112 static void offsetLineStarts(textDisp *textD, int newTopLineNum);
113 static void calcLineStarts(textDisp *textD, int startLine, int endLine);
114 static void calcLastChar(textDisp *textD);
115 static int posToVisibleLineNum(textDisp *textD, int pos, int *lineNum);
116 static void redisplayLine(textDisp *textD, int visLineNum, int leftClip,
117         int rightClip, int leftCharIndex, int rightCharIndex);
118 static void drawString(textDisp *textD, int style, int x, int y, int toX,
119         char *string, int nChars);
120 static void clearRect(textDisp *textD, GC gc, int x, int y,
121         int width, int height);
122 static void drawCursor(textDisp *textD, int x, int y);
123 static int styleOfPos(textDisp *textD, int lineStartPos,
124         int lineLen, int lineIndex, int dispIndex, int thisChar);
125 static int stringWidth(const textDisp* textD, const char* string,
126         int length, int style);
127 static int inSelection(selection *sel, int pos, int lineStartPos,
128         int dispIndex);
129 static int xyToPos(textDisp *textD, int x, int y, int posType);
130 static void xyToUnconstrainedPos(textDisp *textD, int x, int y, int *row,
131         int *column, int posType);
132 static void bufPreDeleteCB(int pos, int nDeleted, void *cbArg);
133 static void bufModifiedCB(int pos, int nInserted, int nDeleted,
134         int nRestyled, const char *deletedText, void *cbArg);
135 static void setScroll(textDisp *textD, int topLineNum, int horizOffset,
136         int updateVScrollBar, int updateHScrollBar);
137 static void hScrollCB(Widget w, XtPointer clientData, XtPointer callData);
138 static void vScrollCB(Widget w, XtPointer clientData, XtPointer callData);
139 static void visibilityEH(Widget w, XtPointer data, XEvent *event,
140         Boolean *continueDispatch);
141 static void redrawLineNumbers(textDisp *textD, int clearAll);
142 static void updateVScrollBarRange(textDisp *textD);
143 static int updateHScrollBarRange(textDisp *textD);
144 static int max(int i1, int i2);
145 static int min(int i1, int i2);
146 static int countLines(const char *string);
147 static int measureVisLine(textDisp *textD, int visLineNum);
148 static int emptyLinesVisible(textDisp *textD);
149 static void blankCursorProtrusions(textDisp *textD);
150 static void allocateFixedFontGCs(textDisp *textD, XFontStruct *fontStruct,
151         Pixel bgPixel, Pixel fgPixel, Pixel selectFGPixel, Pixel selectBGPixel,
152         Pixel highlightFGPixel, Pixel highlightBGPixel, Pixel lineNumFGPixel);
153 static GC allocateGC(Widget w, unsigned long valueMask,
154         unsigned long foreground, unsigned long background, Font font,
155         unsigned long dynamicMask, unsigned long dontCareMask);
156 static void releaseGC(Widget w, GC gc);
157 static void resetClipRectangles(textDisp *textD);
158 static int visLineLength(textDisp *textD, int visLineNum);
159 static void measureDeletedLines(textDisp *textD, int pos, int nDeleted);
160 static void findWrapRange(textDisp *textD, const char *deletedText, int pos,
161         int nInserted, int nDeleted, int *modRangeStart, int *modRangeEnd,
162         int *linesInserted, int *linesDeleted);
163 static void wrappedLineCounter(const textDisp* textD, const textBuffer* buf,
164         int startPos, int maxPos, int maxLines,
165         Boolean startPosIsLineStart, int styleBufOffset,
166         int* retPos, int* retLines, int* retLineStart, int* retLineEnd);
167 static void findLineEnd(textDisp *textD, int startPos, int startPosIsLineStart,
168         int *lineEnd, int *nextLineStart);
169 static int wrapUsesCharacter(textDisp *textD, int lineEndPos);
170 static void hideOrShowHScrollBar(textDisp *textD);
171 static int rangeTouchesRectSel(selection *sel, int rangeStart, int rangeEnd);
172 static void extendRangeForStyleMods(textDisp *textD, int *start, int *end);
173 static int getAbsTopLineNum(textDisp *textD);
174 static void offsetAbsLineNum(textDisp *textD, int oldFirstChar);
175 static int maintainingAbsTopLineNum(textDisp *textD);
176 static void resetAbsLineNum(textDisp *textD);
177 static int measurePropChar(const textDisp* textD, char c,
178         int colNum, int pos);
179 static Pixel allocBGColor(Widget w, char *colorName, int *ok);
180 static Pixel getRangesetColor(textDisp *textD, int ind, Pixel bground);
181 static void textDRedisplayRange(textDisp *textD, int start, int end);
182 
TextDCreate(Widget widget,Widget hScrollBar,Widget vScrollBar,Position left,Position top,Position width,Position height,Position lineNumLeft,Position lineNumWidth,textBuffer * buffer,XFontStruct * fontStruct,Pixel bgPixel,Pixel fgPixel,Pixel selectFGPixel,Pixel selectBGPixel,Pixel highlightFGPixel,Pixel highlightBGPixel,Pixel cursorFGPixel,Pixel lineNumFGPixel,int continuousWrap,int wrapMargin,XmString bgClassString,Pixel calltipFGPixel,Pixel calltipBGPixel)183 textDisp *TextDCreate(Widget widget, Widget hScrollBar, Widget vScrollBar,
184         Position left, Position top, Position width, Position height,
185         Position lineNumLeft, Position lineNumWidth, textBuffer *buffer,
186         XFontStruct *fontStruct, Pixel bgPixel, Pixel fgPixel,
187         Pixel selectFGPixel, Pixel selectBGPixel, Pixel highlightFGPixel,
188         Pixel highlightBGPixel, Pixel cursorFGPixel, Pixel lineNumFGPixel,
189         int continuousWrap, int wrapMargin, XmString bgClassString,
190         Pixel calltipFGPixel, Pixel calltipBGPixel)
191 {
192     textDisp *textD;
193     XGCValues gcValues;
194     int i;
195 
196     textD = (textDisp *)NEditMalloc(sizeof(textDisp));
197     textD->w = widget;
198     textD->top = top;
199     textD->left = left;
200     textD->width = width;
201     textD->height = height;
202     textD->cursorOn = True;
203     textD->cursorPos = 0;
204     textD->cursorX = -100;
205     textD->cursorY = -100;
206     textD->cursorToHint = NO_HINT;
207     textD->cursorStyle = NORMAL_CURSOR;
208     textD->cursorPreferredCol = -1;
209     textD->buffer = buffer;
210     textD->firstChar = 0;
211     textD->lastChar = 0;
212     textD->nBufferLines = 0;
213     textD->topLineNum = 1;
214     textD->absTopLineNum = 1;
215     textD->needAbsTopLineNum = False;
216     textD->horizOffset = 0;
217     textD->visibility = VisibilityUnobscured;
218     textD->hScrollBar = hScrollBar;
219     textD->vScrollBar = vScrollBar;
220     textD->fontStruct = fontStruct;
221     textD->ascent = fontStruct->ascent;
222     textD->descent = fontStruct->descent;
223     textD->fixedFontWidth = fontStruct->min_bounds.width ==
224     	    fontStruct->max_bounds.width ? fontStruct->min_bounds.width : -1;
225     textD->styleBuffer = NULL;
226     textD->styleTable = NULL;
227     textD->nStyles = 0;
228     textD->bgPixel = bgPixel;
229     textD->fgPixel = fgPixel;
230     textD->selectFGPixel = selectFGPixel;
231     textD->highlightFGPixel = highlightFGPixel;
232     textD->selectBGPixel = selectBGPixel;
233     textD->highlightBGPixel = highlightBGPixel;
234     textD->lineNumFGPixel = lineNumFGPixel;
235     textD->cursorFGPixel = cursorFGPixel;
236     textD->wrapMargin = wrapMargin;
237     textD->continuousWrap = continuousWrap;
238     allocateFixedFontGCs(textD, fontStruct, bgPixel, fgPixel, selectFGPixel,
239             selectBGPixel, highlightFGPixel, highlightBGPixel, lineNumFGPixel);
240     textD->styleGC = allocateGC(textD->w, 0, 0, 0, fontStruct->fid,
241             GCClipMask|GCForeground|GCBackground, GCArcMode);
242     textD->lineNumLeft = lineNumLeft;
243     textD->lineNumWidth = lineNumWidth;
244     textD->nVisibleLines = (height - 1) / (textD->ascent + textD->descent) + 1;
245     gcValues.foreground = cursorFGPixel;
246     textD->cursorFGGC = XtGetGC(widget, GCForeground, &gcValues);
247     textD->lineStarts = (int *)NEditMalloc(sizeof(int) * textD->nVisibleLines);
248     textD->lineStarts[0] = 0;
249     textD->calltipW = NULL;
250     textD->calltipShell = NULL;
251     textD->calltip.ID = 0;
252     textD->calltipFGPixel = calltipFGPixel;
253     textD->calltipBGPixel = calltipBGPixel;
254     for (i=1; i<textD->nVisibleLines; i++)
255     	textD->lineStarts[i] = -1;
256     textD->bgClassPixel = NULL;
257     textD->bgClass = NULL;
258     TextDSetupBGClasses(widget, bgClassString, &textD->bgClassPixel,
259           &textD->bgClass, bgPixel);
260     textD->suppressResync = 0;
261     textD->nLinesDeleted = 0;
262     textD->modifyingTabDist = 0;
263     textD->pointerHidden = False;
264     textD->graphicsExposeQueue = NULL;
265 
266     /* Attach an event handler to the widget so we can know the visibility
267        (used for choosing the fastest drawing method) */
268     XtAddEventHandler(widget, VisibilityChangeMask, False,
269         visibilityEH, textD);
270 
271     /* Attach the callback to the text buffer for receiving modification
272        information */
273     if (buffer != NULL) {
274 	BufAddModifyCB(buffer, bufModifiedCB, textD);
275 	BufAddPreDeleteCB(buffer, bufPreDeleteCB, textD);
276     }
277 
278     /* Initialize the scroll bars and attach movement callbacks */
279     if (vScrollBar != NULL) {
280 	XtVaSetValues(vScrollBar, XmNminimum, 1, XmNmaximum, 2,
281     		XmNsliderSize, 1, XmNrepeatDelay, 10, XmNvalue, 1, NULL);
282 	XtAddCallback(vScrollBar, XmNdragCallback, vScrollCB, (XtPointer)textD);
283 	XtAddCallback(vScrollBar, XmNvalueChangedCallback, vScrollCB,
284 		(XtPointer)textD);
285     }
286     if (hScrollBar != NULL) {
287 	XtVaSetValues(hScrollBar, XmNminimum, 0, XmNmaximum, 1,
288     		XmNsliderSize, 1, XmNrepeatDelay, 10, XmNvalue, 0,
289     		XmNincrement, fontStruct->max_bounds.width, NULL);
290 	XtAddCallback(hScrollBar, XmNdragCallback, hScrollCB, (XtPointer)textD);
291 	XtAddCallback(hScrollBar, XmNvalueChangedCallback, hScrollCB,
292 		(XtPointer)textD);
293     }
294 
295     /* Update the display to reflect the contents of the buffer */
296     if (buffer != NULL)
297     	bufModifiedCB(0, buffer->length, 0, 0, NULL, textD);
298 
299     /* Decide if the horizontal scroll bar needs to be visible */
300     hideOrShowHScrollBar(textD);
301 
302     return textD;
303 }
304 
305 /*
306 ** Free a text display and release its associated memory.  Note, the text
307 ** BUFFER that the text display displays is a separate entity and is not
308 ** freed, nor are the style buffer or style table.
309 */
TextDFree(textDisp * textD)310 void TextDFree(textDisp *textD)
311 {
312     BufRemoveModifyCB(textD->buffer, bufModifiedCB, textD);
313     BufRemovePreDeleteCB(textD->buffer, bufPreDeleteCB, textD);
314     releaseGC(textD->w, textD->gc);
315     releaseGC(textD->w, textD->selectGC);
316     releaseGC(textD->w, textD->highlightGC);
317     releaseGC(textD->w, textD->selectBGGC);
318     releaseGC(textD->w, textD->highlightBGGC);
319     releaseGC(textD->w, textD->styleGC);
320     releaseGC(textD->w, textD->lineNumGC);
321     NEditFree(textD->lineStarts);
322     while (TextDPopGraphicExposeQueueEntry(textD)) {
323     }
324     NEditFree(textD->bgClassPixel);
325     NEditFree(textD->bgClass);
326     NEditFree(textD);
327 }
328 
329 /*
330 ** Attach a text buffer to display, replacing the current buffer (if any)
331 */
TextDSetBuffer(textDisp * textD,textBuffer * buffer)332 void TextDSetBuffer(textDisp *textD, textBuffer *buffer)
333 {
334     /* If the text display is already displaying a buffer, clear it off
335        of the display and remove our callback from it */
336     if (textD->buffer != NULL) {
337     	bufModifiedCB(0, 0, textD->buffer->length, 0, NULL, textD);
338     	BufRemoveModifyCB(textD->buffer, bufModifiedCB, textD);
339     	BufRemovePreDeleteCB(textD->buffer, bufPreDeleteCB, textD);
340     }
341 
342     /* Add the buffer to the display, and attach a callback to the buffer for
343        receiving modification information when the buffer contents change */
344     textD->buffer = buffer;
345     BufAddModifyCB(buffer, bufModifiedCB, textD);
346     BufAddPreDeleteCB(buffer, bufPreDeleteCB, textD);
347 
348     /* Update the display */
349     bufModifiedCB(0, buffer->length, 0, 0, NULL, textD);
350 }
351 
352 /*
353 ** Attach (or remove) highlight information in text display and redisplay.
354 ** Highlighting information consists of a style buffer which parallels the
355 ** normal text buffer, but codes font and color information for the display;
356 ** a style table which translates style buffer codes (indexed by buffer
357 ** character - 65 (ASCII code for 'A')) into fonts and colors; and a callback
358 ** mechanism for as-needed highlighting, triggered by a style buffer entry of
359 ** "unfinishedStyle".  Style buffer can trigger additional redisplay during
360 ** a normal buffer modification if the buffer contains a primary selection
361 ** (see extendRangeForStyleMods for more information on this protocol).
362 **
363 ** Style buffers, tables and their associated memory are managed by the caller.
364 */
TextDAttachHighlightData(textDisp * textD,textBuffer * styleBuffer,styleTableEntry * styleTable,int nStyles,char unfinishedStyle,unfinishedStyleCBProc unfinishedHighlightCB,void * cbArg)365 void TextDAttachHighlightData(textDisp *textD, textBuffer *styleBuffer,
366     	styleTableEntry *styleTable, int nStyles, char unfinishedStyle,
367     	unfinishedStyleCBProc unfinishedHighlightCB, void *cbArg)
368 {
369     textD->styleBuffer = styleBuffer;
370     textD->styleTable = styleTable;
371     textD->nStyles = nStyles;
372     textD->unfinishedStyle = unfinishedStyle;
373     textD->unfinishedHighlightCB = unfinishedHighlightCB;
374     textD->highlightCBArg = cbArg;
375 
376     /* Call TextDSetFont to combine font information from style table and
377        primary font, adjust font-related parameters, and then redisplay */
378     TextDSetFont(textD, textD->fontStruct);
379 }
380 
381 
382 /* Change the (non syntax-highlit) colors */
TextDSetColors(textDisp * textD,Pixel textFgP,Pixel textBgP,Pixel selectFgP,Pixel selectBgP,Pixel hiliteFgP,Pixel hiliteBgP,Pixel lineNoFgP,Pixel cursorFgP)383 void TextDSetColors(textDisp *textD, Pixel textFgP, Pixel textBgP,
384         Pixel selectFgP, Pixel selectBgP, Pixel hiliteFgP, Pixel hiliteBgP,
385         Pixel lineNoFgP, Pixel cursorFgP)
386 {
387     XGCValues values;
388     Display *d = XtDisplay(textD->w);
389 
390     /* Update the stored pixels */
391     textD->fgPixel = textFgP;
392     textD->bgPixel = textBgP;
393     textD->selectFGPixel = selectFgP;
394     textD->selectBGPixel = selectBgP;
395     textD->highlightFGPixel = hiliteFgP;
396     textD->highlightBGPixel = hiliteBgP;
397     textD->lineNumFGPixel = lineNoFgP;
398     textD->cursorFGPixel = cursorFgP;
399 
400     releaseGC(textD->w, textD->gc);
401     releaseGC(textD->w, textD->selectGC);
402     releaseGC(textD->w, textD->selectBGGC);
403     releaseGC(textD->w, textD->highlightGC);
404     releaseGC(textD->w, textD->highlightBGGC);
405     releaseGC(textD->w, textD->lineNumGC);
406     allocateFixedFontGCs(textD, textD->fontStruct, textBgP, textFgP, selectFgP,
407             selectBgP, hiliteFgP, hiliteBgP, lineNoFgP);
408 
409     /* Change the cursor GC (the cursor GC is not shared). */
410     values.foreground = cursorFgP;
411     XChangeGC( d, textD->cursorFGGC, GCForeground, &values );
412 
413     /* Redisplay */
414     TextDRedisplayRect(textD, textD->left, textD->top, textD->width,
415                        textD->height);
416     redrawLineNumbers(textD, True);
417 }
418 
419 /*
420 ** Change the (non highlight) font
421 */
TextDSetFont(textDisp * textD,XFontStruct * fontStruct)422 void TextDSetFont(textDisp *textD, XFontStruct *fontStruct)
423 {
424     Display *display = XtDisplay(textD->w);
425     int i, maxAscent = fontStruct->ascent, maxDescent = fontStruct->descent;
426     int width, height, fontWidth;
427     Pixel bgPixel, fgPixel, selectFGPixel, selectBGPixel;
428     Pixel highlightFGPixel, highlightBGPixel, lineNumFGPixel;
429     XGCValues values;
430     XFontStruct *styleFont;
431 
432     /* If font size changes, cursor will be redrawn in a new position */
433     blankCursorProtrusions(textD);
434 
435     /* If there is a (syntax highlighting) style table in use, find the new
436        maximum font height for this text display */
437     for (i=0; i<textD->nStyles; i++) {
438         styleFont = textD->styleTable[i].font;
439         if (styleFont != NULL && styleFont->ascent > maxAscent)
440             maxAscent = styleFont->ascent;
441         if (styleFont != NULL && styleFont->descent > maxDescent)
442             maxDescent = styleFont->descent;
443     }
444     textD->ascent = maxAscent;
445     textD->descent = maxDescent;
446 
447     /* If all of the current fonts are fixed and match in width, compute */
448     fontWidth = fontStruct->max_bounds.width;
449     if (fontWidth != fontStruct->min_bounds.width)
450         fontWidth = -1;
451     else {
452         for (i=0; i<textD->nStyles; i++) {
453             styleFont = textD->styleTable[i].font;
454             if (styleFont != NULL &&
455                     (styleFont->max_bounds.width != fontWidth ||
456                     styleFont->max_bounds.width != styleFont->min_bounds.width))
457                 fontWidth = -1;
458         }
459     }
460     textD->fixedFontWidth = fontWidth;
461 
462     /* Don't let the height dip below one line, or bad things can happen */
463     if (textD->height < maxAscent + maxDescent)
464         textD->height = maxAscent + maxDescent;
465 
466     /* Change the font.  In most cases, this means re-allocating the
467        affected GCs (they are shared with other widgets, and if the primary
468        font changes, must be re-allocated to change it). Unfortunately,
469        this requres recovering all of the colors from the existing GCs */
470     textD->fontStruct = fontStruct;
471     XGetGCValues(display, textD->gc, GCForeground|GCBackground, &values);
472     fgPixel = values.foreground;
473     bgPixel = values.background;
474     XGetGCValues(display, textD->selectGC, GCForeground|GCBackground, &values);
475     selectFGPixel = values.foreground;
476     selectBGPixel = values.background;
477     XGetGCValues(display, textD->highlightGC,GCForeground|GCBackground,&values);
478     highlightFGPixel = values.foreground;
479     highlightBGPixel = values.background;
480     XGetGCValues(display, textD->lineNumGC, GCForeground, &values);
481     lineNumFGPixel = values.foreground;
482     releaseGC(textD->w, textD->gc);
483     releaseGC(textD->w, textD->selectGC);
484     releaseGC(textD->w, textD->highlightGC);
485     releaseGC(textD->w, textD->selectBGGC);
486     releaseGC(textD->w, textD->highlightBGGC);
487     releaseGC(textD->w, textD->lineNumGC);
488     allocateFixedFontGCs(textD, fontStruct, bgPixel, fgPixel, selectFGPixel,
489             selectBGPixel, highlightFGPixel, highlightBGPixel, lineNumFGPixel);
490     XSetFont(display, textD->styleGC, fontStruct->fid);
491 
492     /* Do a full resize to force recalculation of font related parameters */
493     width = textD->width;
494     height = textD->height;
495     textD->width = textD->height = 0;
496     TextDResize(textD, width, height);
497 
498     /* if the shell window doesn't get resized, and the new fonts are
499        of smaller sizes, sometime we get some residual text on the
500        blank space at the bottom part of text area. Clear it here. */
501     clearRect(textD, textD->gc, textD->left,
502 	    textD->top + textD->height - maxAscent - maxDescent,
503 	    textD->width, maxAscent + maxDescent);
504 
505     /* Redisplay */
506     TextDRedisplayRect(textD, textD->left, textD->top, textD->width,
507             textD->height);
508 
509     /* Clean up line number area in case spacing has changed */
510     redrawLineNumbers(textD, True);
511 }
512 
TextDMinFontWidth(textDisp * textD,Boolean considerStyles)513 int TextDMinFontWidth(textDisp *textD, Boolean considerStyles)
514 {
515     int fontWidth = textD->fontStruct->max_bounds.width;
516     int i;
517 
518     if (considerStyles) {
519         for (i = 0; i < textD->nStyles; ++i) {
520             int thisWidth = (textD->styleTable[i].font)->min_bounds.width;
521             if (thisWidth < fontWidth) {
522                 fontWidth = thisWidth;
523             }
524         }
525     }
526     return(fontWidth);
527 }
528 
TextDMaxFontWidth(textDisp * textD,Boolean considerStyles)529 int TextDMaxFontWidth(textDisp *textD, Boolean considerStyles)
530 {
531     int fontWidth = textD->fontStruct->max_bounds.width;
532     int i;
533 
534     if (considerStyles) {
535         for (i = 0; i < textD->nStyles; ++i) {
536             int thisWidth = (textD->styleTable[i].font)->max_bounds.width;
537             if (thisWidth > fontWidth) {
538                 fontWidth = thisWidth;
539             }
540         }
541     }
542     return(fontWidth);
543 }
544 
545 /*
546 ** Change the size of the displayed text area
547 */
TextDResize(textDisp * textD,int width,int height)548 void TextDResize(textDisp *textD, int width, int height)
549 {
550     int oldVisibleLines = textD->nVisibleLines;
551     int canRedraw = XtWindow(textD->w) != 0;
552     int newVisibleLines = height / (textD->ascent + textD->descent);
553     int redrawAll = False;
554     int oldWidth = textD->width;
555     int exactHeight = height - height % (textD->ascent + textD->descent);
556 
557     textD->width = width;
558     textD->height = height;
559 
560     /* In continuous wrap mode, a change in width affects the total number of
561        lines in the buffer, and can leave the top line number incorrect, and
562        the top character no longer pointing at a valid line start */
563     if (textD->continuousWrap && textD->wrapMargin==0 && width!=oldWidth) {
564         int oldFirstChar = textD->firstChar;
565         textD->nBufferLines = TextDCountLines(textD, 0, textD->buffer->length,
566                 True);
567         textD->firstChar = TextDStartOfLine(textD, textD->firstChar);
568         textD->topLineNum = TextDCountLines(textD, 0, textD->firstChar, True)+1;
569         redrawAll = True;
570         offsetAbsLineNum(textD, oldFirstChar);
571     }
572 
573     /* reallocate and update the line starts array, which may have changed
574        size and/or contents. (contents can change in continuous wrap mode
575        when the width changes, even without a change in height) */
576     if (oldVisibleLines < newVisibleLines) {
577         NEditFree(textD->lineStarts);
578         textD->lineStarts = (int *)NEditMalloc(sizeof(int) * newVisibleLines);
579     }
580     textD->nVisibleLines = newVisibleLines;
581     calcLineStarts(textD, 0, newVisibleLines);
582     calcLastChar(textD);
583 
584     /* if the window became shorter, there may be partially drawn
585        text left at the bottom edge, which must be cleaned up */
586     if (canRedraw && oldVisibleLines>newVisibleLines && exactHeight!=height)
587         XClearArea(XtDisplay(textD->w), XtWindow(textD->w), textD->left,
588                 textD->top + exactHeight,  textD->width,
589                 height - exactHeight, False);
590 
591     /* if the window became taller, there may be an opportunity to display
592        more text by scrolling down */
593     if (canRedraw && oldVisibleLines < newVisibleLines && textD->topLineNum +
594             textD->nVisibleLines > textD->nBufferLines)
595         setScroll(textD, max(1, textD->nBufferLines - textD->nVisibleLines +
596                                 2 + TEXT_OF_TEXTD(textD).cursorVPadding),
597                   textD->horizOffset, False, False);
598 
599     /* Update the scroll bar page increment size (as well as other scroll
600        bar parameters.  If updating the horizontal range caused scrolling,
601        redraw */
602     updateVScrollBarRange(textD);
603     if (updateHScrollBarRange(textD))
604         redrawAll = True;
605 
606     /* If a full redraw is needed */
607     if (redrawAll && canRedraw)
608         TextDRedisplayRect(textD, textD->left, textD->top, textD->width,
609                 textD->height);
610 
611     /* Decide if the horizontal scroll bar needs to be visible */
612     hideOrShowHScrollBar(textD);
613 
614     /* Refresh the line number display to draw more line numbers, or
615        erase extras */
616     redrawLineNumbers(textD, True);
617 
618     /* Redraw the calltip */
619     TextDRedrawCalltip(textD, 0);
620 }
621 
622 /*
623 ** Refresh a rectangle of the text display.  left and top are in coordinates of
624 ** the text drawing window
625 */
TextDRedisplayRect(textDisp * textD,int left,int top,int width,int height)626 void TextDRedisplayRect(textDisp *textD, int left, int top, int width,
627 	int height)
628 {
629     int fontHeight, firstLine, lastLine, line;
630 
631     /* find the line number range of the display */
632     fontHeight = textD->ascent + textD->descent;
633     firstLine = (top - textD->top - fontHeight + 1) / fontHeight;
634     lastLine = (top + height - textD->top) / fontHeight;
635 
636     /* If the graphics contexts are shared using XtAllocateGC, their
637        clipping rectangles may have changed since the last use */
638     resetClipRectangles(textD);
639 
640     /* draw the lines of text */
641     for (line=firstLine; line<=lastLine; line++)
642     	redisplayLine(textD, line, left, left+width, 0, INT_MAX);
643 
644     /* draw the line numbers if exposed area includes them */
645     if (textD->lineNumWidth != 0 && left <= textD->lineNumLeft + textD->lineNumWidth)
646 	redrawLineNumbers(textD, False);
647 }
648 
649 /*
650 ** Refresh all of the text between buffer positions "start" and "end"
651 ** not including the character at the position "end".
652 ** If end points beyond the end of the buffer, refresh the whole display
653 ** after pos, including blank lines which are not technically part of
654 ** any range of characters.
655 */
textDRedisplayRange(textDisp * textD,int start,int end)656 static void textDRedisplayRange(textDisp *textD, int start, int end)
657 {
658     int i, startLine, lastLine, startIndex, endIndex;
659 
660     /* If the range is outside of the displayed text, just return */
661     if (end < textD->firstChar || (start > textD->lastChar &&
662     	    !emptyLinesVisible(textD)))
663         return;
664 
665     /* Clean up the starting and ending values */
666     if (start < 0) start = 0;
667     if (start > textD->buffer->length) start = textD->buffer->length;
668     if (end < 0) end = 0;
669     if (end > textD->buffer->length) end = textD->buffer->length;
670 
671     /* Get the starting and ending lines */
672     if (start < textD->firstChar) {
673     	start = textD->firstChar;
674     }
675 
676     if (!posToVisibleLineNum(textD, start, &startLine)) {
677     	startLine = textD->nVisibleLines - 1;
678     }
679 
680     if (end >= textD->lastChar) {
681     	lastLine = textD->nVisibleLines - 1;
682     } else {
683     	if (!posToVisibleLineNum(textD, end, &lastLine)) {
684     	    /* shouldn't happen */
685     	    lastLine = textD->nVisibleLines - 1;
686     	}
687     }
688 
689     /* Get the starting and ending positions within the lines */
690     startIndex = (textD->lineStarts[startLine] == -1)
691             ? 0
692             : start - textD->lineStarts[startLine];
693     if (end >= textD->lastChar)
694     {
695         /*  Request to redisplay beyond textD->lastChar, so tell
696             redisplayLine() to display everything to infy.  */
697         endIndex = INT_MAX;
698     } else if (textD->lineStarts[lastLine] == -1)
699     {
700         /*  Here, lastLine is determined by posToVisibleLineNum() (see
701             if/else above) but deemed to be out of display according to
702             textD->lineStarts. */
703         endIndex = 0;
704     } else
705     {
706         endIndex = end - textD->lineStarts[lastLine];
707     }
708 
709     /* Reset the clipping rectangles for the drawing GCs which are shared
710        using XtAllocateGC, and may have changed since the last use */
711     resetClipRectangles(textD);
712 
713     /* If the starting and ending lines are the same, redisplay the single
714        line between "start" and "end" */
715     if (startLine == lastLine) {
716         redisplayLine(textD, startLine, 0, INT_MAX, startIndex, endIndex);
717     	return;
718     }
719 
720     /* Redisplay the first line from "start" */
721     redisplayLine(textD, startLine, 0, INT_MAX, startIndex, INT_MAX);
722 
723     /* Redisplay the lines in between at their full width */
724     for (i=startLine+1; i<lastLine; i++)
725 	redisplayLine(textD, i, 0, INT_MAX, 0, INT_MAX);
726 
727     /* Redisplay the last line to "end" */
728     redisplayLine(textD, lastLine, 0, INT_MAX, 0, endIndex);
729 }
730 
731 /*
732 ** Set the scroll position of the text display vertically by line number and
733 ** horizontally by pixel offset from the left margin
734 */
TextDSetScroll(textDisp * textD,int topLineNum,int horizOffset)735 void TextDSetScroll(textDisp *textD, int topLineNum, int horizOffset)
736 {
737     int sliderSize, sliderMax;
738     int vPadding = (int)(TEXT_OF_TEXTD(textD).cursorVPadding);
739 
740     /* Limit the requested scroll position to allowable values */
741     if (topLineNum < 1)
742         topLineNum = 1;
743     else if ((topLineNum > textD->topLineNum) &&
744              (topLineNum > (textD->nBufferLines + 2 - textD->nVisibleLines +
745                           vPadding)))
746         topLineNum = max(textD->topLineNum,
747                 textD->nBufferLines + 2 - textD->nVisibleLines + vPadding);
748     XtVaGetValues(textD->hScrollBar, XmNmaximum, &sliderMax,
749             XmNsliderSize, &sliderSize, NULL);
750     if (horizOffset < 0)
751         horizOffset = 0;
752     if (horizOffset > sliderMax - sliderSize)
753         horizOffset = sliderMax - sliderSize;
754 
755     setScroll(textD, topLineNum, horizOffset, True, True);
756 }
757 
758 /*
759 ** Get the current scroll position for the text display, in terms of line
760 ** number of the top line and horizontal pixel offset from the left margin
761 */
TextDGetScroll(textDisp * textD,int * topLineNum,int * horizOffset)762 void TextDGetScroll(textDisp *textD, int *topLineNum, int *horizOffset)
763 {
764     *topLineNum = textD->topLineNum;
765     *horizOffset = textD->horizOffset;
766 }
767 
768 /*
769 ** Set the position of the text insertion cursor for text display "textD"
770 */
TextDSetInsertPosition(textDisp * textD,int newPos)771 void TextDSetInsertPosition(textDisp *textD, int newPos)
772 {
773     /* make sure new position is ok, do nothing if it hasn't changed */
774     if (newPos == textD->cursorPos)
775     	return;
776     if (newPos < 0) newPos = 0;
777     if (newPos > textD->buffer->length) newPos = textD->buffer->length;
778 
779     /* cursor movement cancels vertical cursor motion column */
780     textD->cursorPreferredCol = -1;
781 
782     /* erase the cursor at it's previous position */
783     TextDBlankCursor(textD);
784 
785     /* draw it at its new position */
786     textD->cursorPos = newPos;
787     textD->cursorOn = True;
788     textDRedisplayRange(textD, textD->cursorPos-1, textD->cursorPos + 1);
789 }
790 
TextDBlankCursor(textDisp * textD)791 void TextDBlankCursor(textDisp *textD)
792 {
793     if (!textD->cursorOn)
794     	return;
795 
796     blankCursorProtrusions(textD);
797     textD->cursorOn = False;
798     textDRedisplayRange(textD, textD->cursorPos-1, textD->cursorPos+1);
799 }
800 
TextDUnblankCursor(textDisp * textD)801 void TextDUnblankCursor(textDisp *textD)
802 {
803     if (!textD->cursorOn) {
804     	textD->cursorOn = True;
805         textDRedisplayRange(textD, textD->cursorPos-1, textD->cursorPos+1);
806     }
807 }
808 
TextDSetCursorStyle(textDisp * textD,int style)809 void TextDSetCursorStyle(textDisp *textD, int style)
810 {
811     textD->cursorStyle = style;
812     blankCursorProtrusions(textD);
813     if (textD->cursorOn) {
814         textDRedisplayRange(textD, textD->cursorPos-1, textD->cursorPos + 1);
815     }
816 }
817 
TextDSetWrapMode(textDisp * textD,int wrap,int wrapMargin)818 void TextDSetWrapMode(textDisp *textD, int wrap, int wrapMargin)
819 {
820     textD->wrapMargin = wrapMargin;
821     textD->continuousWrap = wrap;
822 
823     /* wrapping can change change the total number of lines, re-count */
824     textD->nBufferLines = TextDCountLines(textD, 0, textD->buffer->length,True);
825 
826     /* changing wrap margins wrap or changing from wrapped mode to non-wrapped
827        can leave the character at the top no longer at a line start, and/or
828        change the line number */
829     textD->firstChar = TextDStartOfLine(textD, textD->firstChar);
830     textD->topLineNum = TextDCountLines(textD, 0, textD->firstChar, True) + 1;
831     resetAbsLineNum(textD);
832 
833     /* update the line starts array */
834     calcLineStarts(textD, 0, textD->nVisibleLines);
835     calcLastChar(textD);
836 
837     /* Update the scroll bar page increment size (as well as other scroll
838        bar parameters) */
839     updateVScrollBarRange(textD);
840     updateHScrollBarRange(textD);
841 
842     /* Decide if the horizontal scroll bar needs to be visible */
843     hideOrShowHScrollBar(textD);
844 
845     /* Do a full redraw */
846     TextDRedisplayRect(textD, 0, textD->top, textD->width + textD->left,
847 	    textD->height);
848 }
849 
TextDGetInsertPosition(textDisp * textD)850 int TextDGetInsertPosition(textDisp *textD)
851 {
852     return textD->cursorPos;
853 }
854 
855 /*
856 ** Insert "text" at the current cursor location.  This has the same
857 ** effect as inserting the text into the buffer using BufInsert and
858 ** then moving the insert position after the newly inserted text, except
859 ** that it's optimized to do less redrawing.
860 */
TextDInsert(textDisp * textD,char * text)861 void TextDInsert(textDisp *textD, char *text)
862 {
863     int pos = textD->cursorPos;
864 
865     textD->cursorToHint = pos + strlen(text);
866     BufInsert(textD->buffer, pos, text);
867     textD->cursorToHint = NO_HINT;
868 }
869 
870 /*
871 ** Insert "text" (which must not contain newlines), overstriking the current
872 ** cursor location.
873 */
TextDOverstrike(textDisp * textD,char * text)874 void TextDOverstrike(textDisp *textD, char *text)
875 {
876     int startPos = textD->cursorPos;
877     textBuffer *buf = textD->buffer;
878     int lineStart = BufStartOfLine(buf, startPos);
879     int textLen = strlen(text);
880     int i, p, endPos, indent, startIndent, endIndent;
881     char *c, ch, *paddedText = NULL;
882 
883     /* determine how many displayed character positions are covered */
884     startIndent = BufCountDispChars(textD->buffer, lineStart, startPos);
885     indent = startIndent;
886     for (c=text; *c!='\0'; c++)
887     	indent += BufCharWidth(*c, indent, buf->tabDist, buf->nullSubsChar);
888     endIndent = indent;
889 
890     /* find which characters to remove, and if necessary generate additional
891        padding to make up for removed control characters at the end */
892     indent=startIndent;
893     for (p=startPos; ; p++) {
894     	if (p == buf->length)
895     	    break;
896     	ch = BufGetCharacter(buf, p);
897     	if (ch == '\n')
898     	    break;
899     	indent += BufCharWidth(ch, indent, buf->tabDist, buf->nullSubsChar);
900     	if (indent == endIndent) {
901     	    p++;
902     	    break;
903     	} else if (indent > endIndent) {
904     	    if (ch != '\t') {
905     	    	p++;
906     	    	paddedText = (char*)NEditMalloc(textLen + MAX_EXP_CHAR_LEN + 1);
907     	    	strcpy(paddedText, text);
908     	    	for (i=0; i<indent-endIndent; i++)
909     	    	    paddedText[textLen+i] = ' ';
910     	    	paddedText[textLen+i] = '\0';
911     	    }
912     	    break;
913     	}
914     }
915     endPos = p;
916 
917     textD->cursorToHint = startPos + textLen;
918     BufReplace(buf, startPos, endPos, paddedText == NULL ? text : paddedText);
919     textD->cursorToHint = NO_HINT;
920     NEditFree(paddedText);
921 }
922 
923 /*
924 ** Translate window coordinates to the nearest text cursor position.
925 */
TextDXYToPosition(textDisp * textD,int x,int y)926 int TextDXYToPosition(textDisp *textD, int x, int y)
927 {
928     return xyToPos(textD, x, y, CURSOR_POS);
929 }
930 
931 /*
932 ** Translate window coordinates to the nearest character cell.
933 */
TextDXYToCharPos(textDisp * textD,int x,int y)934 int TextDXYToCharPos(textDisp *textD, int x, int y)
935 {
936     return xyToPos(textD, x, y, CHARACTER_POS);
937 }
938 
939 /*
940 ** Translate window coordinates to the nearest row and column number for
941 ** positioning the cursor.  This, of course, makes no sense when the font
942 ** is proportional, since there are no absolute columns.
943 */
TextDXYToUnconstrainedPosition(textDisp * textD,int x,int y,int * row,int * column)944 void TextDXYToUnconstrainedPosition(textDisp *textD, int x, int y, int *row,
945 	int *column)
946 {
947     xyToUnconstrainedPos(textD, x, y, row, column, CURSOR_POS);
948 }
949 
950 /*
951 ** Translate line and column to the nearest row and column number for
952 ** positioning the cursor.  This, of course, makes no sense when the font
953 ** is proportional, since there are no absolute columns.
954 */
TextDLineAndColToPos(textDisp * textD,int lineNum,int column)955 int TextDLineAndColToPos(textDisp *textD, int lineNum, int column)
956 {
957     int i, lineEnd, charIndex, outIndex;
958     int lineStart=0, charLen=0;
959     char *lineStr, expandedChar[MAX_EXP_CHAR_LEN];
960 
961     /* Count lines */
962     if (lineNum < 1)
963         lineNum = 1;
964     lineEnd = -1;
965     for (i=1; i<=lineNum && lineEnd<textD->buffer->length; i++) {
966         lineStart = lineEnd + 1;
967         lineEnd = BufEndOfLine(textD->buffer, lineStart);
968     }
969 
970     /* If line is beyond end of buffer, position at last character in buffer */
971     if ( lineNum >= i ) {
972       return lineEnd;
973     }
974 
975     /* Start character index at zero */
976     charIndex=0;
977 
978     /* Only have to count columns if column isn't zero (or negative) */
979     if (column > 0) {
980       /* Count columns, expanding each character */
981       lineStr = BufGetRange(textD->buffer, lineStart, lineEnd);
982       outIndex = 0;
983       for(i=lineStart; i<lineEnd; i++, charIndex++) {
984           charLen = BufExpandCharacter(lineStr[charIndex], outIndex,
985                   expandedChar, textD->buffer->tabDist,
986                   textD->buffer->nullSubsChar);
987           if ( outIndex+charLen >= column ) break;
988           outIndex+=charLen;
989       }
990 
991       /* If the column is in the middle of an expanded character, put cursor
992        * in front of character if in first half of character, and behind
993        * character if in last half of character
994        */
995       if (column >= outIndex + ( charLen / 2 ))
996         charIndex++;
997 
998       /* If we are beyond the end of the line, back up one space */
999       if ((i>=lineEnd)&&(charIndex>0)) charIndex--;
1000     }
1001 
1002     /* Position is the start of the line plus the index into line buffer */
1003     return lineStart + charIndex;
1004 }
1005 
1006 /*
1007 ** Translate a buffer text position to the XY location where the center
1008 ** of the cursor would be positioned to point to that character.  Returns
1009 ** False if the position is not displayed because it is VERTICALLY out
1010 ** of view.  If the position is horizontally out of view, returns the
1011 ** x coordinate where the position would be if it were visible.
1012 */
TextDPositionToXY(textDisp * textD,int pos,int * x,int * y)1013 int TextDPositionToXY(textDisp *textD, int pos, int *x, int *y)
1014 {
1015     int charIndex, lineStartPos, fontHeight, lineLen;
1016     int visLineNum, charLen, outIndex, xStep, charStyle;
1017     char *lineStr, expandedChar[MAX_EXP_CHAR_LEN];
1018 
1019     /* If position is not displayed, return false */
1020     if (pos < textD->firstChar ||
1021     	    (pos > textD->lastChar && !emptyLinesVisible(textD)))
1022     	return False;
1023 
1024     /* Calculate y coordinate */
1025     if (!posToVisibleLineNum(textD, pos, &visLineNum))
1026     	return False;
1027     fontHeight = textD->ascent + textD->descent;
1028     *y = textD->top + visLineNum*fontHeight + fontHeight/2;
1029 
1030     /* Get the text, length, and  buffer position of the line. If the position
1031        is beyond the end of the buffer and should be at the first position on
1032        the first empty line, don't try to get or scan the text  */
1033     lineStartPos = textD->lineStarts[visLineNum];
1034     if (lineStartPos == -1) {
1035     	*x = textD->left - textD->horizOffset;
1036     	return True;
1037     }
1038     lineLen = visLineLength(textD, visLineNum);
1039     lineStr = BufGetRange(textD->buffer, lineStartPos, lineStartPos + lineLen);
1040 
1041     /* Step through character positions from the beginning of the line
1042        to "pos" to calculate the x coordinate */
1043     xStep = textD->left - textD->horizOffset;
1044     outIndex = 0;
1045     for(charIndex=0; charIndex<pos-lineStartPos; charIndex++) {
1046     	charLen = BufExpandCharacter(lineStr[charIndex], outIndex, expandedChar,
1047     		textD->buffer->tabDist, textD->buffer->nullSubsChar);
1048    	charStyle = styleOfPos(textD, lineStartPos, lineLen, charIndex,
1049    	    	outIndex, lineStr[charIndex]);
1050     	xStep += stringWidth(textD, expandedChar, charLen, charStyle);
1051     	outIndex += charLen;
1052     }
1053     *x = xStep;
1054     NEditFree(lineStr);
1055     return True;
1056 }
1057 
1058 /*
1059 ** If the text widget is maintaining a line number count appropriate to "pos"
1060 ** return the line and column numbers of pos, otherwise return False.  If
1061 ** continuous wrap mode is on, returns the absolute line number (as opposed to
1062 ** the wrapped line number which is used for scrolling).  THIS ROUTINE ONLY
1063 ** WORKS FOR DISPLAYED LINES AND, IN CONTINUOUS WRAP MODE, ONLY WHEN THE
1064 ** ABSOLUTE LINE NUMBER IS BEING MAINTAINED.  Otherwise, it returns False.
1065 */
TextDPosToLineAndCol(textDisp * textD,int pos,int * lineNum,int * column)1066 int TextDPosToLineAndCol(textDisp *textD, int pos, int *lineNum, int *column)
1067 {
1068     textBuffer *buf = textD->buffer;
1069 
1070     /* In continuous wrap mode, the absolute (non-wrapped) line count is
1071        maintained separately, as needed.  Only return it if we're actually
1072        keeping track of it and pos is in the displayed text */
1073     if (textD->continuousWrap) {
1074 	if (!maintainingAbsTopLineNum(textD) || pos < textD->firstChar ||
1075 		pos > textD->lastChar)
1076 	    return False;
1077 	*lineNum = textD->absTopLineNum + BufCountLines(buf,
1078 		textD->firstChar, pos);
1079 	*column = BufCountDispChars(buf, BufStartOfLine(buf, pos), pos);
1080 	return True;
1081     }
1082 
1083     /* Only return the data if pos is within the displayed text */
1084     if (!posToVisibleLineNum(textD, pos, lineNum))
1085 	return False;
1086     *column = BufCountDispChars(buf, textD->lineStarts[*lineNum], pos);
1087     *lineNum += textD->topLineNum;
1088     return True;
1089 }
1090 
1091 /*
1092 ** Return True if position (x, y) is inside of the primary selection
1093 */
TextDInSelection(textDisp * textD,int x,int y)1094 int TextDInSelection(textDisp *textD, int x, int y)
1095 {
1096     int row, column, pos = xyToPos(textD, x, y, CHARACTER_POS);
1097     textBuffer *buf = textD->buffer;
1098 
1099     xyToUnconstrainedPos(textD, x, y, &row, &column, CHARACTER_POS);
1100     if (rangeTouchesRectSel(&buf->primary, textD->firstChar, textD->lastChar))
1101     	column = TextDOffsetWrappedColumn(textD, row, column);
1102     return inSelection(&buf->primary, pos, BufStartOfLine(buf, pos), column);
1103 }
1104 
1105 /*
1106 ** Correct a column number based on an unconstrained position (as returned by
1107 ** TextDXYToUnconstrainedPosition) to be relative to the last actual newline
1108 ** in the buffer before the row and column position given, rather than the
1109 ** last line start created by line wrapping.  This is an adapter
1110 ** for rectangular selections and code written before continuous wrap mode,
1111 ** which thinks that the unconstrained column is the number of characters
1112 ** from the last newline.  Obviously this is time consuming, because it
1113 ** invloves character re-counting.
1114 */
TextDOffsetWrappedColumn(textDisp * textD,int row,int column)1115 int TextDOffsetWrappedColumn(textDisp *textD, int row, int column)
1116 {
1117     int lineStart, dispLineStart;
1118 
1119     if (!textD->continuousWrap || row < 0 || row > textD->nVisibleLines)
1120     	return column;
1121     dispLineStart = textD->lineStarts[row];
1122     if (dispLineStart == -1)
1123     	return column;
1124     lineStart = BufStartOfLine(textD->buffer, dispLineStart);
1125     return column + BufCountDispChars(textD->buffer, lineStart, dispLineStart);
1126 }
1127 
1128 /*
1129 ** Correct a row number from an unconstrained position (as returned by
1130 ** TextDXYToUnconstrainedPosition) to a straight number of newlines from the
1131 ** top line of the display.  Because rectangular selections are based on
1132 ** newlines, rather than display wrapping, and anywhere a rectangular selection
1133 ** needs a row, it needs it in terms of un-wrapped lines.
1134 */
TextDOffsetWrappedRow(textDisp * textD,int row)1135 int TextDOffsetWrappedRow(textDisp *textD, int row)
1136 {
1137     if (!textD->continuousWrap || row < 0 || row > textD->nVisibleLines)
1138     	return row;
1139     return BufCountLines(textD->buffer, textD->firstChar,
1140     	    textD->lineStarts[row]);
1141 }
1142 
1143 /*
1144 ** Scroll the display to bring insertion cursor into view.
1145 **
1146 ** Note: it would be nice to be able to do this without counting lines twice
1147 ** (setScroll counts them too) and/or to count from the most efficient
1148 ** starting point, but the efficiency of this routine is not as important to
1149 ** the overall performance of the text display.
1150 */
TextDMakeInsertPosVisible(textDisp * textD)1151 void TextDMakeInsertPosVisible(textDisp *textD)
1152 {
1153     int hOffset, topLine, x, y;
1154     int cursorPos = textD->cursorPos;
1155     int linesFromTop = 0, do_padding = 1;
1156     int cursorVPadding = (int)TEXT_OF_TEXTD(textD).cursorVPadding;
1157 
1158     hOffset = textD->horizOffset;
1159     topLine = textD->topLineNum;
1160 
1161     /* Don't do padding if this is a mouse operation */
1162     do_padding = ((TEXT_OF_TEXTD(textD).dragState == NOT_CLICKED) &&
1163                   (cursorVPadding > 0));
1164 
1165     /* Find the new top line number */
1166     if (cursorPos < textD->firstChar) {
1167         topLine -= TextDCountLines(textD, cursorPos, textD->firstChar, False);
1168         /* linesFromTop = 0; */
1169     } else if (cursorPos > textD->lastChar && !emptyLinesVisible(textD)) {
1170         topLine += TextDCountLines(textD, textD->lastChar -
1171                 (wrapUsesCharacter(textD, textD->lastChar) ? 0 : 1),
1172                 cursorPos, False);
1173         linesFromTop = textD->nVisibleLines-1;
1174     } else if (cursorPos == textD->lastChar && !emptyLinesVisible(textD) &&
1175             !wrapUsesCharacter(textD, textD->lastChar)) {
1176         topLine++;
1177         linesFromTop = textD->nVisibleLines-1;
1178     } else {
1179         /* Avoid extra counting if cursorVPadding is disabled */
1180         if (do_padding)
1181             linesFromTop = TextDCountLines(textD, textD->firstChar,
1182                     cursorPos, True);
1183     }
1184     if (topLine < 1) {
1185         fprintf(stderr, "nedit: internal consistency check tl1 failed\n");
1186         topLine = 1;
1187     }
1188 
1189     if (do_padding) {
1190         /* Keep the cursor away from the top or bottom of screen. */
1191         if (textD->nVisibleLines <= 2*(int)cursorVPadding) {
1192             topLine += (linesFromTop - textD->nVisibleLines/2);
1193             topLine = max(topLine, 1);
1194         } else if (linesFromTop < (int)cursorVPadding) {
1195             topLine -= (cursorVPadding - linesFromTop);
1196             topLine = max(topLine, 1);
1197         } else if (linesFromTop > textD->nVisibleLines-(int)cursorVPadding-1) {
1198             topLine += (linesFromTop - (textD->nVisibleLines-cursorVPadding-1));
1199         }
1200     }
1201 
1202     /* Find the new setting for horizontal offset (this is a bit ungraceful).
1203        If the line is visible, just use TextDPositionToXY to get the position
1204        to scroll to, otherwise, do the vertical scrolling first, then the
1205        horizontal */
1206     if (!TextDPositionToXY(textD, cursorPos, &x, &y)) {
1207         setScroll(textD, topLine, hOffset, True, True);
1208         if (!TextDPositionToXY(textD, cursorPos, &x, &y))
1209             return; /* Give up, it's not worth it (but why does it fail?) */
1210     }
1211     if (x > textD->left + textD->width)
1212         hOffset += x - (textD->left + textD->width);
1213     else if (x < textD->left)
1214         hOffset += x - textD->left;
1215 
1216     /* Do the scroll */
1217     setScroll(textD, topLine, hOffset, True, True);
1218 }
1219 
1220 /*
1221 ** Return the current preferred column along with the current
1222 ** visible line index (-1 if not visible) and the lineStartPos
1223 ** of the current insert position.
1224 */
TextDPreferredColumn(textDisp * textD,int * visLineNum,int * lineStartPos)1225 int TextDPreferredColumn(textDisp *textD, int *visLineNum, int *lineStartPos)
1226 {
1227     int column;
1228 
1229     /* Find the position of the start of the line.  Use the line starts array
1230     if possible, to avoid unbounded line-counting in continuous wrap mode */
1231     if (posToVisibleLineNum(textD, textD->cursorPos, visLineNum)) {
1232         *lineStartPos = textD->lineStarts[*visLineNum];
1233     }
1234     else {
1235         *lineStartPos = TextDStartOfLine(textD, textD->cursorPos);
1236         *visLineNum = -1;
1237     }
1238 
1239     /* Decide what column to move to, if there's a preferred column use that */
1240     column = (textD->cursorPreferredCol >= 0)
1241             ? textD->cursorPreferredCol
1242             : BufCountDispChars(textD->buffer, *lineStartPos, textD->cursorPos);
1243     return(column);
1244 }
1245 
1246 /*
1247 ** Return the insert position of the requested column given
1248 ** the lineStartPos.
1249 */
TextDPosOfPreferredCol(textDisp * textD,int column,int lineStartPos)1250 int TextDPosOfPreferredCol(textDisp *textD, int column, int lineStartPos)
1251 {
1252     int newPos;
1253 
1254     newPos = BufCountForwardDispChars(textD->buffer, lineStartPos, column);
1255     if (textD->continuousWrap) {
1256         newPos = min(newPos, TextDEndOfLine(textD, lineStartPos, True));
1257     }
1258     return(newPos);
1259 }
1260 
1261 /*
1262 ** Cursor movement functions
1263 */
TextDMoveRight(textDisp * textD)1264 int TextDMoveRight(textDisp *textD)
1265 {
1266     if (textD->cursorPos >= textD->buffer->length)
1267     	return False;
1268     TextDSetInsertPosition(textD, textD->cursorPos + 1);
1269     return True;
1270 }
1271 
TextDMoveLeft(textDisp * textD)1272 int TextDMoveLeft(textDisp *textD)
1273 {
1274     if (textD->cursorPos <= 0)
1275     	return False;
1276     TextDSetInsertPosition(textD, textD->cursorPos - 1);
1277     return True;
1278 }
1279 
TextDMoveUp(textDisp * textD,int absolute)1280 int TextDMoveUp(textDisp *textD, int absolute)
1281 {
1282     int lineStartPos, column, prevLineStartPos, newPos, visLineNum;
1283 
1284     /* Find the position of the start of the line.  Use the line starts array
1285        if possible, to avoid unbounded line-counting in continuous wrap mode */
1286     if (absolute) {
1287         lineStartPos = BufStartOfLine(textD->buffer, textD->cursorPos);
1288         visLineNum = -1;
1289     } else if (posToVisibleLineNum(textD, textD->cursorPos, &visLineNum))
1290     	lineStartPos = textD->lineStarts[visLineNum];
1291     else {
1292     	lineStartPos = TextDStartOfLine(textD, textD->cursorPos);
1293     	visLineNum = -1;
1294     }
1295     if (lineStartPos == 0)
1296     	return False;
1297 
1298     /* Decide what column to move to, if there's a preferred column use that */
1299     column = textD->cursorPreferredCol >= 0
1300             ? textD->cursorPreferredCol
1301             : BufCountDispChars(textD->buffer, lineStartPos, textD->cursorPos);
1302 
1303     /* count forward from the start of the previous line to reach the column */
1304     if (absolute) {
1305         prevLineStartPos = BufCountBackwardNLines(textD->buffer, lineStartPos, 1);
1306     } else if (visLineNum != -1 && visLineNum != 0) {
1307         prevLineStartPos = textD->lineStarts[visLineNum-1];
1308     } else {
1309         prevLineStartPos = TextDCountBackwardNLines(textD, lineStartPos, 1);
1310     }
1311 
1312     newPos = BufCountForwardDispChars(textD->buffer, prevLineStartPos, column);
1313     if (textD->continuousWrap && !absolute)
1314     	newPos = min(newPos, TextDEndOfLine(textD, prevLineStartPos, True));
1315 
1316     /* move the cursor */
1317     TextDSetInsertPosition(textD, newPos);
1318 
1319     /* if a preferred column wasn't aleady established, establish it */
1320     textD->cursorPreferredCol = column;
1321 
1322     return True;
1323 }
1324 
TextDMoveDown(textDisp * textD,int absolute)1325 int TextDMoveDown(textDisp *textD, int absolute)
1326 {
1327     int lineStartPos, column, nextLineStartPos, newPos, visLineNum;
1328 
1329     if (textD->cursorPos == textD->buffer->length) {
1330         return False;
1331     }
1332 
1333     if (absolute) {
1334         lineStartPos = BufStartOfLine(textD->buffer, textD->cursorPos);
1335         visLineNum = -1;
1336     } else if (posToVisibleLineNum(textD, textD->cursorPos, &visLineNum)) {
1337         lineStartPos = textD->lineStarts[visLineNum];
1338     } else {
1339         lineStartPos = TextDStartOfLine(textD, textD->cursorPos);
1340         visLineNum = -1;
1341     }
1342 
1343     column = textD->cursorPreferredCol >= 0
1344             ? textD->cursorPreferredCol
1345             : BufCountDispChars(textD->buffer, lineStartPos, textD->cursorPos);
1346 
1347     if (absolute)
1348         nextLineStartPos = BufCountForwardNLines(textD->buffer, lineStartPos, 1);
1349     else
1350         nextLineStartPos = TextDCountForwardNLines(textD, lineStartPos, 1, True);
1351 
1352     newPos = BufCountForwardDispChars(textD->buffer, nextLineStartPos, column);
1353 
1354     if (textD->continuousWrap && !absolute) {
1355         newPos = min(newPos, TextDEndOfLine(textD, nextLineStartPos, True));
1356     }
1357 
1358     TextDSetInsertPosition(textD, newPos);
1359     textD->cursorPreferredCol = column;
1360 
1361     return True;
1362 }
1363 
1364 /*
1365 ** Same as BufCountLines, but takes in to account wrapping if wrapping is
1366 ** turned on.  If the caller knows that startPos is at a line start, it
1367 ** can pass "startPosIsLineStart" as True to make the call more efficient
1368 ** by avoiding the additional step of scanning back to the last newline.
1369 */
TextDCountLines(textDisp * textD,int startPos,int endPos,int startPosIsLineStart)1370 int TextDCountLines(textDisp *textD, int startPos, int endPos,
1371     	int startPosIsLineStart)
1372 {
1373     int retLines, retPos, retLineStart, retLineEnd;
1374 
1375     /* If we're not wrapping use simple (and more efficient) BufCountLines */
1376     if (!textD->continuousWrap)
1377     	return BufCountLines(textD->buffer, startPos, endPos);
1378 
1379     wrappedLineCounter(textD, textD->buffer, startPos, endPos, INT_MAX,
1380 	    startPosIsLineStart, 0, &retPos, &retLines, &retLineStart,
1381 	    &retLineEnd);
1382     return retLines;
1383 }
1384 
1385 /*
1386 ** Same as BufCountForwardNLines, but takes in to account line breaks when
1387 ** wrapping is turned on. If the caller knows that startPos is at a line start,
1388 ** it can pass "startPosIsLineStart" as True to make the call more efficient
1389 ** by avoiding the additional step of scanning back to the last newline.
1390 */
TextDCountForwardNLines(const textDisp * textD,int startPos,unsigned nLines,Boolean startPosIsLineStart)1391 int TextDCountForwardNLines(const textDisp* textD, int startPos,
1392         unsigned nLines, Boolean startPosIsLineStart)
1393 {
1394     int retLines, retPos, retLineStart, retLineEnd;
1395 
1396     /* if we're not wrapping use more efficient BufCountForwardNLines */
1397     if (!textD->continuousWrap)
1398     	return BufCountForwardNLines(textD->buffer, startPos, nLines);
1399 
1400     /* wrappedLineCounter can't handle the 0 lines case */
1401     if (nLines == 0)
1402     	return startPos;
1403 
1404     /* use the common line counting routine to count forward */
1405     wrappedLineCounter(textD, textD->buffer, startPos, textD->buffer->length,
1406     	    nLines, startPosIsLineStart, 0, &retPos, &retLines, &retLineStart,
1407     	    &retLineEnd);
1408     return retPos;
1409 }
1410 
1411 /*
1412 ** Same as BufEndOfLine, but takes in to account line breaks when wrapping
1413 ** is turned on.  If the caller knows that startPos is at a line start, it
1414 ** can pass "startPosIsLineStart" as True to make the call more efficient
1415 ** by avoiding the additional step of scanning back to the last newline.
1416 **
1417 ** Note that the definition of the end of a line is less clear when continuous
1418 ** wrap is on.  With continuous wrap off, it's just a pointer to the newline
1419 ** that ends the line.  When it's on, it's the character beyond the last
1420 ** DISPLAYABLE character on the line, where a whitespace character which has
1421 ** been "converted" to a newline for wrapping is not considered displayable.
1422 ** Also note that, a line can be wrapped at a non-whitespace character if the
1423 ** line had no whitespace.  In this case, this routine returns a pointer to
1424 ** the start of the next line.  This is also consistent with the model used by
1425 ** visLineLength.
1426 */
TextDEndOfLine(const textDisp * textD,int pos,Boolean startPosIsLineStart)1427 int TextDEndOfLine(const textDisp* textD, int pos,
1428         Boolean startPosIsLineStart)
1429 {
1430     int retLines, retPos, retLineStart, retLineEnd;
1431 
1432     /* If we're not wrapping use more efficient BufEndOfLine */
1433     if (!textD->continuousWrap)
1434     	return BufEndOfLine(textD->buffer, pos);
1435 
1436     if (pos == textD->buffer->length)
1437     	return pos;
1438     wrappedLineCounter(textD, textD->buffer, pos, textD->buffer->length, 1,
1439     	    startPosIsLineStart, 0, &retPos, &retLines, &retLineStart,
1440 	    &retLineEnd);
1441     return retLineEnd;
1442 }
1443 
1444 /*
1445 ** Same as BufStartOfLine, but returns the character after last wrap point
1446 ** rather than the last newline.
1447 */
TextDStartOfLine(const textDisp * textD,int pos)1448 int TextDStartOfLine(const textDisp* textD, int pos)
1449 {
1450     int retLines, retPos, retLineStart, retLineEnd;
1451 
1452     /* If we're not wrapping, use the more efficient BufStartOfLine */
1453     if (!textD->continuousWrap)
1454     	return BufStartOfLine(textD->buffer, pos);
1455 
1456     wrappedLineCounter(textD, textD->buffer, BufStartOfLine(textD->buffer, pos),
1457     	    pos, INT_MAX, True, 0, &retPos, &retLines, &retLineStart,
1458 	    &retLineEnd);
1459     return retLineStart;
1460 }
1461 
1462 /*
1463 ** Same as BufCountBackwardNLines, but takes in to account line breaks when
1464 ** wrapping is turned on.
1465 */
TextDCountBackwardNLines(textDisp * textD,int startPos,int nLines)1466 int TextDCountBackwardNLines(textDisp *textD, int startPos, int nLines)
1467 {
1468     textBuffer *buf = textD->buffer;
1469     int pos, lineStart, retLines, retPos, retLineStart, retLineEnd;
1470 
1471     /* If we're not wrapping, use the more efficient BufCountBackwardNLines */
1472     if (!textD->continuousWrap)
1473     	return BufCountBackwardNLines(textD->buffer, startPos, nLines);
1474 
1475     pos = startPos;
1476     while (True) {
1477 	lineStart = BufStartOfLine(buf, pos);
1478 	wrappedLineCounter(textD, textD->buffer, lineStart, pos, INT_MAX,
1479 	    	True, 0, &retPos, &retLines, &retLineStart, &retLineEnd);
1480 	if (retLines > nLines)
1481     	    return TextDCountForwardNLines(textD, lineStart, retLines-nLines,
1482     	    	    True);
1483     	nLines -= retLines;
1484     	pos = lineStart - 1;
1485     	if (pos < 0)
1486     	    return 0;
1487     	nLines -= 1;
1488     }
1489 }
1490 
1491 /*
1492 ** Callback attached to the text buffer to receive delete information before
1493 ** the modifications are actually made.
1494 */
bufPreDeleteCB(int pos,int nDeleted,void * cbArg)1495 static void bufPreDeleteCB(int pos, int nDeleted, void *cbArg)
1496 {
1497     textDisp *textD = (textDisp *)cbArg;
1498     if (textD->continuousWrap &&
1499         (textD->fixedFontWidth == -1 || textD->modifyingTabDist))
1500 	/* Note: we must perform this measurement, even if there is not a
1501 	   single character deleted; the number of "deleted" lines is the
1502 	   number of visual lines spanned by the real line in which the
1503 	   modification takes place.
1504 	   Also, a modification of the tab distance requires the same
1505 	   kind of calculations in advance, even if the font width is "fixed",
1506 	   because when the width of the tab characters changes, the layout
1507 	   of the text may be completely different. */
1508 	measureDeletedLines(textD, pos, nDeleted);
1509     else
1510 	textD->suppressResync = 0; /* Probably not needed, but just in case */
1511 }
1512 
1513 /*
1514 ** Callback attached to the text buffer to receive modification information
1515 */
bufModifiedCB(int pos,int nInserted,int nDeleted,int nRestyled,const char * deletedText,void * cbArg)1516 static void bufModifiedCB(int pos, int nInserted, int nDeleted,
1517 	int nRestyled, const char *deletedText, void *cbArg)
1518 {
1519     int linesInserted, linesDeleted, startDispPos, endDispPos;
1520     textDisp *textD = (textDisp *)cbArg;
1521     textBuffer *buf = textD->buffer;
1522     int oldFirstChar = textD->firstChar;
1523     int scrolled, origCursorPos = textD->cursorPos;
1524     int wrapModStart, wrapModEnd;
1525 
1526     /* buffer modification cancels vertical cursor motion column */
1527     if (nInserted != 0 || nDeleted != 0)
1528     	textD->cursorPreferredCol = -1;
1529 
1530     /* Count the number of lines inserted and deleted, and in the case
1531        of continuous wrap mode, how much has changed */
1532     if (textD->continuousWrap) {
1533     	findWrapRange(textD, deletedText, pos, nInserted, nDeleted,
1534     	    	&wrapModStart, &wrapModEnd, &linesInserted, &linesDeleted);
1535     } else {
1536 	linesInserted = nInserted == 0 ? 0 :
1537     		BufCountLines(buf, pos, pos + nInserted);
1538 	linesDeleted = nDeleted == 0 ? 0 : countLines(deletedText);
1539     }
1540 
1541     /* Update the line starts and topLineNum */
1542     if (nInserted != 0 || nDeleted != 0) {
1543 	if (textD->continuousWrap) {
1544 	    updateLineStarts(textD, wrapModStart, wrapModEnd-wrapModStart,
1545 	    	    nDeleted + pos-wrapModStart + (wrapModEnd-(pos+nInserted)),
1546 	    	    linesInserted, linesDeleted, &scrolled);
1547 	} else {
1548 	    updateLineStarts(textD, pos, nInserted, nDeleted, linesInserted,
1549     		    linesDeleted, &scrolled);
1550 	}
1551     } else
1552     	scrolled = False;
1553 
1554     /* If we're counting non-wrapped lines as well, maintain the absolute
1555        (non-wrapped) line number of the text displayed */
1556     if (maintainingAbsTopLineNum(textD) && (nInserted != 0 || nDeleted != 0)) {
1557 	if (pos + nDeleted < oldFirstChar)
1558 	    textD->absTopLineNum += BufCountLines(buf, pos, pos + nInserted) -
1559 		    countLines(deletedText);
1560 	else if (pos < oldFirstChar)
1561 	    resetAbsLineNum(textD);
1562     }
1563 
1564     /* Update the line count for the whole buffer */
1565     textD->nBufferLines += linesInserted - linesDeleted;
1566 
1567     /* Update the scroll bar ranges (and value if the value changed).  Note
1568        that updating the horizontal scroll bar range requires scanning the
1569        entire displayed text, however, it doesn't seem to hurt performance
1570        much.  Note also, that the horizontal scroll bar update routine is
1571        allowed to re-adjust horizOffset if there is blank space to the right
1572        of all lines of text. */
1573     updateVScrollBarRange(textD);
1574     scrolled |= updateHScrollBarRange(textD);
1575 
1576     /* Update the cursor position */
1577     if (textD->cursorToHint != NO_HINT) {
1578     	textD->cursorPos = textD->cursorToHint;
1579     	textD->cursorToHint = NO_HINT;
1580     } else if (textD->cursorPos > pos) {
1581     	if (textD->cursorPos < pos + nDeleted)
1582     	    textD->cursorPos = pos;
1583     	else
1584     	    textD->cursorPos += nInserted - nDeleted;
1585     }
1586 
1587     /* If the changes caused scrolling, re-paint everything and we're done. */
1588     if (scrolled) {
1589     	blankCursorProtrusions(textD);
1590     	TextDRedisplayRect(textD, 0, textD->top, textD->width + textD->left,
1591 		textD->height);
1592         if (textD->styleBuffer) {/* See comments in extendRangeForStyleMods */
1593     	    textD->styleBuffer->primary.selected = False;
1594             textD->styleBuffer->primary.zeroWidth = False;
1595         }
1596     	return;
1597     }
1598 
1599     /* If the changes didn't cause scrolling, decide the range of characters
1600        that need to be re-painted.  Also if the cursor position moved, be
1601        sure that the redisplay range covers the old cursor position so the
1602        old cursor gets erased, and erase the bits of the cursor which extend
1603        beyond the left and right edges of the text. */
1604     startDispPos = textD->continuousWrap ? wrapModStart : pos;
1605     if (origCursorPos == startDispPos && textD->cursorPos != startDispPos)
1606     	startDispPos = min(startDispPos, origCursorPos-1);
1607     if (linesInserted == linesDeleted) {
1608         if (nInserted == 0 && nDeleted == 0)
1609             endDispPos = pos + nRestyled;
1610         else {
1611     	    endDispPos = textD->continuousWrap ? wrapModEnd :
1612     	    	    BufEndOfLine(buf, pos + nInserted) + 1;
1613     	    if (origCursorPos >= startDispPos &&
1614     	    	    (origCursorPos <= endDispPos || endDispPos == buf->length))
1615     	    	blankCursorProtrusions(textD);
1616     	}
1617         /* If more than one line is inserted/deleted, a line break may have
1618            been inserted or removed in between, and the line numbers may
1619            have changed. If only one line is altered, line numbers cannot
1620            be affected (the insertion or removal of a line break always
1621            results in at least two lines being redrawn). */
1622 	if (linesInserted > 1) redrawLineNumbers(textD, False);
1623     } else { /* linesInserted != linesDeleted */
1624     	endDispPos = textD->lastChar + 1;
1625     	if (origCursorPos >= pos)
1626     	    blankCursorProtrusions(textD);
1627 	redrawLineNumbers(textD, False);
1628     }
1629 
1630     /* If there is a style buffer, check if the modification caused additional
1631        changes that need to be redisplayed.  (Redisplaying separately would
1632        cause double-redraw on almost every modification involving styled
1633        text).  Extend the redraw range to incorporate style changes */
1634     if (textD->styleBuffer)
1635     	extendRangeForStyleMods(textD, &startDispPos, &endDispPos);
1636 
1637     /* Redisplay computed range */
1638     textDRedisplayRange(textD, startDispPos, endDispPos);
1639 }
1640 
1641 /*
1642 ** In continuous wrap mode, internal line numbers are calculated after
1643 ** wrapping.  A separate non-wrapped line count is maintained when line
1644 ** numbering is turned on.  There is some performance cost to maintaining this
1645 ** line count, so normally absolute line numbers are not tracked if line
1646 ** numbering is off.  This routine allows callers to specify that they still
1647 ** want this line count maintained (for use via TextDPosToLineAndCol).
1648 ** More specifically, this allows the line number reported in the statistics
1649 ** line to be calibrated in absolute lines, rather than post-wrapped lines.
1650 */
TextDMaintainAbsLineNum(textDisp * textD,int state)1651 void TextDMaintainAbsLineNum(textDisp *textD, int state)
1652 {
1653     textD->needAbsTopLineNum = state;
1654     resetAbsLineNum(textD);
1655 }
1656 
1657 /*
1658 ** Returns the absolute (non-wrapped) line number of the first line displayed.
1659 ** Returns 0 if the absolute top line number is not being maintained.
1660 */
getAbsTopLineNum(textDisp * textD)1661 static int getAbsTopLineNum(textDisp *textD)
1662 {
1663     if (!textD->continuousWrap)
1664 	return textD->topLineNum;
1665     if (maintainingAbsTopLineNum(textD))
1666 	return textD->absTopLineNum;
1667     return 0;
1668 }
1669 
1670 /*
1671 ** Re-calculate absolute top line number for a change in scroll position.
1672 */
offsetAbsLineNum(textDisp * textD,int oldFirstChar)1673 static void offsetAbsLineNum(textDisp *textD, int oldFirstChar)
1674 {
1675     if (maintainingAbsTopLineNum(textD)) {
1676 	if (textD->firstChar < oldFirstChar)
1677 	    textD->absTopLineNum -= BufCountLines(textD->buffer,
1678 		    textD->firstChar, oldFirstChar);
1679 	else
1680 	    textD->absTopLineNum += BufCountLines(textD->buffer,
1681 		    oldFirstChar, textD->firstChar);
1682     }
1683 }
1684 
1685 /*
1686 ** Return true if a separate absolute top line number is being maintained
1687 ** (for displaying line numbers or showing in the statistics line).
1688 */
maintainingAbsTopLineNum(textDisp * textD)1689 static int maintainingAbsTopLineNum(textDisp *textD)
1690 {
1691     return textD->continuousWrap &&
1692 	    (textD->lineNumWidth != 0 || textD->needAbsTopLineNum);
1693 }
1694 
1695 /*
1696 ** Count lines from the beginning of the buffer to reestablish the
1697 ** absolute (non-wrapped) top line number.  If mode is not continuous wrap,
1698 ** or the number is not being maintained, does nothing.
1699 */
resetAbsLineNum(textDisp * textD)1700 static void resetAbsLineNum(textDisp *textD)
1701 {
1702     textD->absTopLineNum = 1;
1703     offsetAbsLineNum(textD, 0);
1704 }
1705 
1706 /*
1707 ** Find the line number of position "pos" relative to the first line of
1708 ** displayed text. Returns False if the line is not displayed.
1709 */
posToVisibleLineNum(textDisp * textD,int pos,int * lineNum)1710 static int posToVisibleLineNum(textDisp *textD, int pos, int *lineNum)
1711 {
1712     int i;
1713 
1714     if (pos < textD->firstChar)
1715     	return False;
1716     if (pos > textD->lastChar) {
1717     	if (emptyLinesVisible(textD)) {
1718     	    if (textD->lastChar < textD->buffer->length) {
1719     		if (!posToVisibleLineNum(textD, textD->lastChar, lineNum)) {
1720     		    fprintf(stderr, "nedit: Consistency check ptvl failed\n");
1721     		    return False;
1722     		}
1723     		return ++(*lineNum) <= textD->nVisibleLines-1;
1724             } else {
1725             	posToVisibleLineNum(textD, max(textD->lastChar-1, 0), lineNum);
1726             	return True;
1727             }
1728 	}
1729 	return False;
1730     }
1731 
1732     for (i=textD->nVisibleLines-1; i>=0; i--) {
1733     	if (textD->lineStarts[i] != -1 && pos >= textD->lineStarts[i]) {
1734     	    *lineNum = i;
1735     	    return True;
1736     	}
1737     }
1738 
1739     return False;
1740 }
1741 
1742 /*
1743 ** Redisplay the text on a single line represented by "visLineNum" (the
1744 ** number of lines down from the top of the display), limited by
1745 ** "leftClip" and "rightClip" window coordinates and "leftCharIndex" and
1746 ** "rightCharIndex" character positions (not including the character at
1747 ** position "rightCharIndex").
1748 **
1749 ** The cursor is also drawn if it appears on the line.
1750 */
redisplayLine(textDisp * textD,int visLineNum,int leftClip,int rightClip,int leftCharIndex,int rightCharIndex)1751 static void redisplayLine(textDisp *textD, int visLineNum, int leftClip,
1752 	int rightClip, int leftCharIndex, int rightCharIndex)
1753 {
1754     textBuffer *buf = textD->buffer;
1755     int i, x, y, startX, charIndex, lineStartPos, lineLen, fontHeight;
1756     int stdCharWidth, charWidth, startIndex, charStyle, style;
1757     int charLen, outStartIndex, outIndex, cursorX = 0, hasCursor = False;
1758     int dispIndexOffset, cursorPos = textD->cursorPos, y_orig;
1759     char expandedChar[MAX_EXP_CHAR_LEN], outStr[MAX_DISP_LINE_LEN];
1760     char *lineStr, *outPtr;
1761     char baseChar;
1762 
1763     /* If line is not displayed, skip it */
1764     if (visLineNum < 0 || visLineNum >= textD->nVisibleLines)
1765     	return;
1766 
1767     /* Shrink the clipping range to the active display area */
1768     leftClip = max(textD->left, leftClip);
1769     rightClip = min(rightClip, textD->left + textD->width);
1770 
1771     if (leftClip > rightClip) {
1772         return;
1773     }
1774 
1775     /* Calculate y coordinate of the string to draw */
1776     fontHeight = textD->ascent + textD->descent;
1777     y = textD->top + visLineNum * fontHeight;
1778 
1779     /* Get the text, length, and  buffer position of the line to display */
1780     lineStartPos = textD->lineStarts[visLineNum];
1781     if (lineStartPos == -1) {
1782     	lineLen = 0;
1783     	lineStr = NULL;
1784     } else {
1785 	lineLen = visLineLength(textD, visLineNum);
1786 	lineStr = BufGetRange(buf, lineStartPos, lineStartPos + lineLen);
1787     }
1788 
1789     /* Space beyond the end of the line is still counted in units of characters
1790        of a standardized character width (this is done mostly because style
1791        changes based on character position can still occur in this region due
1792        to rectangular selections).  stdCharWidth must be non-zero to prevent a
1793        potential infinite loop if x does not advance */
1794     stdCharWidth = textD->fontStruct->max_bounds.width;
1795     if (stdCharWidth <= 0) {
1796     	fprintf(stderr, "nedit: Internal Error, bad font measurement\n");
1797     	NEditFree(lineStr);
1798     	return;
1799     }
1800 
1801     /* Rectangular selections are based on "real" line starts (after a newline
1802        or start of buffer).  Calculate the difference between the last newline
1803        position and the line start we're using.  Since scanning back to find a
1804        newline is expensive, only do so if there's actually a rectangular
1805        selection which needs it */
1806     if (textD->continuousWrap && (rangeTouchesRectSel(&buf->primary,
1807     	    lineStartPos, lineStartPos + lineLen) || rangeTouchesRectSel(
1808     	    &buf->secondary, lineStartPos, lineStartPos + lineLen) ||
1809     	    rangeTouchesRectSel(&buf->highlight, lineStartPos,
1810     	    lineStartPos + lineLen))) {
1811     	dispIndexOffset = BufCountDispChars(buf,
1812     	    	BufStartOfLine(buf, lineStartPos), lineStartPos);
1813     } else
1814     	dispIndexOffset = 0;
1815 
1816     /* Step through character positions from the beginning of the line (even if
1817        that's off the left edge of the displayed area) to find the first
1818        character position that's not clipped, and the x coordinate for drawing
1819        that character */
1820     x = textD->left - textD->horizOffset;
1821     outIndex = 0;
1822 
1823     for (charIndex = 0; ; charIndex++) {
1824         baseChar = '\0';
1825         charLen = charIndex >= lineLen
1826                 ? 1
1827                 : BufExpandCharacter(baseChar = lineStr[charIndex], outIndex,
1828                         expandedChar, buf->tabDist, buf->nullSubsChar);
1829     	style = styleOfPos(textD, lineStartPos, lineLen, charIndex,
1830                 outIndex + dispIndexOffset, baseChar);
1831         charWidth = charIndex >= lineLen
1832                 ? stdCharWidth
1833                 : stringWidth(textD, expandedChar, charLen, style);
1834 
1835     	if (x + charWidth >= leftClip && charIndex >= leftCharIndex) {
1836     	    startIndex = charIndex;
1837     	    outStartIndex = outIndex;
1838     	    startX = x;
1839     	    break;
1840     	}
1841     	x += charWidth;
1842     	outIndex += charLen;
1843     }
1844 
1845     /* Scan character positions from the beginning of the clipping range, and
1846        draw parts whenever the style changes (also note if the cursor is on
1847        this line, and where it should be drawn to take advantage of the x
1848        position which we've gone to so much trouble to calculate) */
1849     outPtr = outStr;
1850     outIndex = outStartIndex;
1851     x = startX;
1852     for (charIndex = startIndex; charIndex < rightCharIndex; charIndex++) {
1853     	if (lineStartPos+charIndex == cursorPos) {
1854     	    if (charIndex < lineLen
1855                     || (charIndex == lineLen && cursorPos >= buf->length)) {
1856     		hasCursor = True;
1857     		cursorX = x - 1;
1858     	    } else if (charIndex == lineLen) {
1859     	    	if (wrapUsesCharacter(textD, cursorPos)) {
1860     	    	    hasCursor = True;
1861     	    	    cursorX = x - 1;
1862     	    	}
1863     	    }
1864     	}
1865 
1866         baseChar = '\0';
1867      	charLen = charIndex >= lineLen
1868                 ? 1
1869                 : BufExpandCharacter(baseChar = lineStr[charIndex], outIndex,
1870                         expandedChar, buf->tabDist, buf->nullSubsChar);
1871    	charStyle = styleOfPos(textD, lineStartPos, lineLen, charIndex,
1872                 outIndex + dispIndexOffset, baseChar);
1873    	for (i = 0; i < charLen; i++) {
1874             if (i != 0 && charIndex < lineLen && lineStr[charIndex] == '\t') {
1875                 charStyle = styleOfPos(textD, lineStartPos, lineLen, charIndex,
1876                         outIndex + dispIndexOffset, '\t');
1877             }
1878 
1879      	    if (charStyle != style) {
1880     		drawString(textD, style, startX, y, x, outStr, outPtr - outStr);
1881     		outPtr = outStr;
1882     		startX = x;
1883     		style = charStyle;
1884     	    }
1885 
1886     	    if (charIndex < lineLen) {
1887     		*outPtr = expandedChar[i];
1888     		charWidth = stringWidth(textD, &expandedChar[i], 1, charStyle);
1889     	    } else {
1890     		charWidth = stdCharWidth;
1891             }
1892 
1893     	    outPtr++;
1894     	    x += charWidth;
1895     	    outIndex++;
1896 	}
1897 
1898         if (outPtr - outStr + MAX_EXP_CHAR_LEN >= MAX_DISP_LINE_LEN
1899                 || x >= rightClip) {
1900     	    break;
1901         }
1902     }
1903 
1904     /* Draw the remaining style segment */
1905     drawString(textD, style, startX, y, x, outStr, outPtr - outStr);
1906 
1907     /* Draw the cursor if part of it appeared on the redisplayed part of
1908        this line.  Also check for the cases which are not caught as the
1909        line is scanned above: when the cursor appears at the very end
1910        of the redisplayed section. */
1911     y_orig = textD->cursorY;
1912     if (textD->cursorOn) {
1913         if (hasCursor) {
1914     	    drawCursor(textD, cursorX, y);
1915         } else if (charIndex < lineLen
1916                 && (lineStartPos+charIndex+1 == cursorPos)
1917 	    	&& x == rightClip) {
1918             if (cursorPos >= buf->length) {
1919     	    	drawCursor(textD, x - 1, y);
1920             } else {
1921                 if (wrapUsesCharacter(textD, cursorPos)) {
1922     	    	    drawCursor(textD, x - 1, y);
1923                 }
1924     	    }
1925         } else if ((lineStartPos + rightCharIndex) == cursorPos) {
1926             drawCursor(textD, x - 1, y);
1927         }
1928     }
1929 
1930     /* If the y position of the cursor has changed, redraw the calltip */
1931     if (hasCursor && (y_orig != textD->cursorY || y_orig != y))
1932         TextDRedrawCalltip(textD, 0);
1933 
1934     NEditFree(lineStr);
1935 }
1936 
1937 /*
1938 ** Draw a string or blank area according to parameter "style", using the
1939 ** appropriate colors and drawing method for that style, with top left
1940 ** corner at x, y.  If style says to draw text, use "string" as source of
1941 ** characters, and draw "nChars", if style is FILL, erase
1942 ** rectangle where text would have drawn from x to toX and from y to
1943 ** the maximum y extent of the current font(s).
1944 */
drawString(textDisp * textD,int style,int x,int y,int toX,char * string,int nChars)1945 static void drawString(textDisp *textD, int style, int x, int y, int toX,
1946 	char *string, int nChars)
1947 {
1948     GC gc, bgGC;
1949     XGCValues gcValues;
1950     XFontStruct *fs = textD->fontStruct;
1951     Pixel bground = textD->bgPixel;
1952     Pixel fground = textD->fgPixel;
1953     int underlineStyle = FALSE;
1954 
1955     /* Don't draw if widget isn't realized */
1956     if (XtWindow(textD->w) == 0)
1957     	return;
1958 
1959     /* select a GC */
1960     if (style & (STYLE_LOOKUP_MASK | BACKLIGHT_MASK | RANGESET_MASK)) {
1961         gc = bgGC = textD->styleGC;
1962     }
1963     else if (style & HIGHLIGHT_MASK) {
1964         gc = textD->highlightGC;
1965         bgGC = textD->highlightBGGC;
1966     }
1967     else if (style & PRIMARY_MASK) {
1968         gc = textD->selectGC;
1969         bgGC = textD->selectBGGC;
1970     }
1971     else {
1972         gc = bgGC = textD->gc;
1973     }
1974 
1975     if (gc == textD->styleGC) {
1976         /* we have work to do */
1977         styleTableEntry *styleRec;
1978         /* Set font, color, and gc depending on style.  For normal text, GCs
1979            for normal drawing, or drawing within a selection or highlight are
1980            pre-allocated and pre-configured.  For syntax highlighting, GCs are
1981            configured here, on the fly. */
1982         if (style & STYLE_LOOKUP_MASK) {
1983             styleRec = &textD->styleTable[(style & STYLE_LOOKUP_MASK) - ASCII_A];
1984             underlineStyle = styleRec->underline;
1985             fs = styleRec->font;
1986             gcValues.font = fs->fid;
1987             fground = styleRec->color;
1988             /* here you could pick up specific select and highlight fground */
1989         }
1990         else {
1991             styleRec = NULL;
1992             gcValues.font = fs->fid;
1993             fground = textD->fgPixel;
1994         }
1995         /* Background color priority order is:
1996            1 Primary(Selection), 2 Highlight(Parens),
1997            3 Rangeset, 4 SyntaxHighlightStyle,
1998            5 Backlight (if NOT fill), 6 DefaultBackground */
1999         bground =
2000             style & PRIMARY_MASK   ? textD->selectBGPixel :
2001             style & HIGHLIGHT_MASK ? textD->highlightBGPixel :
2002             style & RANGESET_MASK  ?
2003                       getRangesetColor(textD,
2004                           (style&RANGESET_MASK)>>RANGESET_SHIFT,
2005                             bground) :
2006             styleRec && styleRec->bgColorName ? styleRec->bgColor :
2007             (style & BACKLIGHT_MASK) && !(style & FILL_MASK) ?
2008                       textD->bgClassPixel[(style>>BACKLIGHT_SHIFT) & 0xff] :
2009             textD->bgPixel;
2010         if (fground == bground) /* B&W kludge */
2011             fground = textD->bgPixel;
2012         /* set up gc for clearing using the foreground color entry */
2013         gcValues.foreground = gcValues.background = bground;
2014         XChangeGC(XtDisplay(textD->w), gc,
2015                 GCFont | GCForeground | GCBackground, &gcValues);
2016     }
2017 
2018     /* Draw blank area rather than text, if that was the request */
2019     if (style & FILL_MASK) {
2020         /* wipes out to right hand edge of widget */
2021 	if (toX >= textD->left)
2022 	    clearRect(textD, bgGC, max(x, textD->left), y,
2023 		    toX - max(x, textD->left), textD->ascent + textD->descent);
2024         return;
2025     }
2026 
2027     /* If any space around the character remains unfilled (due to use of
2028        different sized fonts for highlighting), fill in above or below
2029        to erase previously drawn characters */
2030     if (fs->ascent < textD->ascent)
2031     	clearRect(textD, bgGC, x, y, toX - x, textD->ascent - fs->ascent);
2032     if (fs->descent < textD->descent)
2033     	clearRect(textD, bgGC, x, y + textD->ascent + fs->descent, toX - x,
2034     		textD->descent - fs->descent);
2035 
2036     /* set up gc for writing text (set foreground properly) */
2037     if (bgGC == textD->styleGC) {
2038         gcValues.foreground = fground;
2039         XChangeGC(XtDisplay(textD->w), gc, GCForeground, &gcValues);
2040     }
2041 
2042     /* Draw the string using gc and font set above */
2043     XDrawImageString(XtDisplay(textD->w), XtWindow(textD->w), gc, x,
2044     	    y + textD->ascent, string, nChars);
2045 
2046     /* Underline if style is secondary selection */
2047     if (style & SECONDARY_MASK || underlineStyle)
2048     {
2049         /* restore foreground in GC (was set to background by clearRect()) */
2050         gcValues.foreground = fground;
2051         XChangeGC(XtDisplay(textD->w), gc,
2052                 GCForeground, &gcValues);
2053         /* draw underline */
2054     	XDrawLine(XtDisplay(textD->w), XtWindow(textD->w), gc, x,
2055     	    	y + textD->ascent, toX - 1, y + textD->ascent);
2056     }
2057 }
2058 
2059 /*
2060 ** Clear a rectangle with the appropriate background color for "style"
2061 */
clearRect(textDisp * textD,GC gc,int x,int y,int width,int height)2062 static void clearRect(textDisp *textD, GC gc, int x, int y,
2063     	int width, int height)
2064 {
2065     /* A width of zero means "clear to end of window" to XClearArea */
2066     if (width == 0 || XtWindow(textD->w) == 0)
2067     	return;
2068 
2069     if (gc == textD->gc) {
2070         XClearArea(XtDisplay(textD->w), XtWindow(textD->w), x, y,
2071                 width, height, False);
2072     }
2073     else {
2074         XFillRectangle(XtDisplay(textD->w), XtWindow(textD->w),
2075                 gc, x, y, width, height);
2076     }
2077 }
2078 
2079 /*
2080 ** Draw a cursor with top center at x, y.
2081 */
drawCursor(textDisp * textD,int x,int y)2082 static void drawCursor(textDisp *textD, int x, int y)
2083 {
2084     XSegment segs[5];
2085     int left, right, cursorWidth, midY;
2086     int fontWidth = textD->fontStruct->min_bounds.width, nSegs = 0;
2087     int fontHeight = textD->ascent + textD->descent;
2088     int bot = y + fontHeight - 1;
2089 
2090     if (XtWindow(textD->w) == 0 || x < textD->left-1 ||
2091 	    x > textD->left + textD->width)
2092     	return;
2093 
2094     /* For cursors other than the block, make them around 2/3 of a character
2095        width, rounded to an even number of pixels so that X will draw an
2096        odd number centered on the stem at x. */
2097     cursorWidth = (fontWidth/3) * 2;
2098     left = x - cursorWidth/2;
2099     right = left + cursorWidth;
2100 
2101     /* Create segments and draw cursor */
2102     if (textD->cursorStyle == CARET_CURSOR) {
2103     	midY = bot - fontHeight/5;
2104     	segs[0].x1 = left; segs[0].y1 = bot; segs[0].x2 = x; segs[0].y2 = midY;
2105     	segs[1].x1 = x; segs[1].y1 = midY; segs[1].x2 = right; segs[1].y2 = bot;
2106     	segs[2].x1 = left; segs[2].y1 = bot; segs[2].x2 = x; segs[2].y2=midY-1;
2107     	segs[3].x1 = x; segs[3].y1=midY-1; segs[3].x2 = right; segs[3].y2 = bot;
2108     	nSegs = 4;
2109     } else if (textD->cursorStyle == NORMAL_CURSOR) {
2110 	segs[0].x1 = left; segs[0].y1 = y; segs[0].x2 = right; segs[0].y2 = y;
2111 	segs[1].x1 = x; segs[1].y1 = y; segs[1].x2 = x; segs[1].y2 = bot;
2112 	segs[2].x1 = left; segs[2].y1 = bot; segs[2].x2 = right; segs[2].y2=bot;
2113 	nSegs = 3;
2114     } else if (textD->cursorStyle == HEAVY_CURSOR) {
2115 	segs[0].x1 = x-1; segs[0].y1 = y; segs[0].x2 = x-1; segs[0].y2 = bot;
2116 	segs[1].x1 = x; segs[1].y1 = y; segs[1].x2 = x; segs[1].y2 = bot;
2117 	segs[2].x1 = x+1; segs[2].y1 = y; segs[2].x2 = x+1; segs[2].y2 = bot;
2118 	segs[3].x1 = left; segs[3].y1 = y; segs[3].x2 = right; segs[3].y2 = y;
2119 	segs[4].x1 = left; segs[4].y1 = bot; segs[4].x2 = right; segs[4].y2=bot;
2120 	nSegs = 5;
2121     } else if (textD->cursorStyle == DIM_CURSOR) {
2122 	midY = y + fontHeight/2;
2123 	segs[0].x1 = x; segs[0].y1 = y; segs[0].x2 = x; segs[0].y2 = y;
2124 	segs[1].x1 = x; segs[1].y1 = midY; segs[1].x2 = x; segs[1].y2 = midY;
2125 	segs[2].x1 = x; segs[2].y1 = bot; segs[2].x2 = x; segs[2].y2 = bot;
2126 	nSegs = 3;
2127     } else if (textD->cursorStyle == BLOCK_CURSOR) {
2128 	right = x + fontWidth;
2129 	segs[0].x1 = x; segs[0].y1 = y; segs[0].x2 = right; segs[0].y2 = y;
2130 	segs[1].x1 = right; segs[1].y1 = y; segs[1].x2 = right; segs[1].y2=bot;
2131 	segs[2].x1 = right; segs[2].y1 = bot; segs[2].x2 = x; segs[2].y2 = bot;
2132 	segs[3].x1 = x; segs[3].y1 = bot; segs[3].x2 = x; segs[3].y2 = y;
2133 	nSegs = 4;
2134     }
2135     XDrawSegments(XtDisplay(textD->w), XtWindow(textD->w),
2136     	    textD->cursorFGGC, segs, nSegs);
2137 
2138     /* Save the last position drawn */
2139     textD->cursorX = x;
2140     textD->cursorY = y;
2141 }
2142 
2143 /*
2144 ** Determine the drawing method to use to draw a specific character from "buf".
2145 ** "lineStartPos" gives the character index where the line begins, "lineIndex",
2146 ** the number of characters past the beginning of the line, and "dispIndex",
2147 ** the number of displayed characters past the beginning of the line.  Passing
2148 ** lineStartPos of -1 returns the drawing style for "no text".
2149 **
2150 ** Why not just: styleOfPos(textD, pos)?  Because style applies to blank areas
2151 ** of the window beyond the text boundaries, and because this routine must also
2152 ** decide whether a position is inside of a rectangular selection, and do so
2153 ** efficiently, without re-counting character positions from the start of the
2154 ** line.
2155 **
2156 ** Note that style is a somewhat incorrect name, drawing method would
2157 ** be more appropriate.
2158 */
styleOfPos(textDisp * textD,int lineStartPos,int lineLen,int lineIndex,int dispIndex,int thisChar)2159 static int styleOfPos(textDisp *textD, int lineStartPos,
2160     	int lineLen, int lineIndex, int dispIndex, int thisChar)
2161 {
2162     textBuffer *buf = textD->buffer;
2163     textBuffer *styleBuf = textD->styleBuffer;
2164     int pos, style = 0;
2165 
2166     if (lineStartPos == -1 || buf == NULL)
2167     	return FILL_MASK;
2168 
2169     pos = lineStartPos + min(lineIndex, lineLen);
2170 
2171     if (lineIndex >= lineLen)
2172    	style = FILL_MASK;
2173     else if (styleBuf != NULL) {
2174     	style = (unsigned char)BufGetCharacter(styleBuf, pos);
2175     	if (style == textD->unfinishedStyle) {
2176     	    /* encountered "unfinished" style, trigger parsing */
2177     	    (textD->unfinishedHighlightCB)(textD, pos, textD->highlightCBArg);
2178     	    style = (unsigned char)BufGetCharacter(styleBuf, pos);
2179     	}
2180     }
2181     if (inSelection(&buf->primary, pos, lineStartPos, dispIndex))
2182     	style |= PRIMARY_MASK;
2183     if (inSelection(&buf->highlight, pos, lineStartPos, dispIndex))
2184     	style |= HIGHLIGHT_MASK;
2185     if (inSelection(&buf->secondary, pos, lineStartPos, dispIndex))
2186     	style |= SECONDARY_MASK;
2187     /* store in the RANGESET_MASK portion of style the rangeset index for pos */
2188     if (buf->rangesetTable) {
2189         int rangesetIndex = RangesetIndex1ofPos(buf->rangesetTable, pos, True);
2190         style |= ((rangesetIndex << RANGESET_SHIFT) & RANGESET_MASK);
2191     }
2192     /* store in the BACKLIGHT_MASK portion of style the background color class
2193        of the character thisChar */
2194     if (textD->bgClass)
2195     {
2196         style |= (textD->bgClass[(unsigned char)thisChar]<<BACKLIGHT_SHIFT);
2197     }
2198     return style;
2199 }
2200 
2201 /*
2202 ** Find the width of a string in the font of a particular style
2203 */
stringWidth(const textDisp * textD,const char * string,int length,int style)2204 static int stringWidth(const textDisp* textD, const char *string,
2205         int length, int style)
2206 {
2207     XFontStruct *fs;
2208 
2209     if (style & STYLE_LOOKUP_MASK)
2210     	fs = textD->styleTable[(style & STYLE_LOOKUP_MASK) - ASCII_A].font;
2211     else
2212     	fs = textD->fontStruct;
2213     return XTextWidth(fs, (char*) string, (int) length);
2214 }
2215 
2216 /*
2217 ** Return true if position "pos" with indentation "dispIndex" is in
2218 ** selection "sel"
2219 */
inSelection(selection * sel,int pos,int lineStartPos,int dispIndex)2220 static int inSelection(selection *sel, int pos, int lineStartPos, int dispIndex)
2221 {
2222     return sel->selected &&
2223     	 ((!sel->rectangular &&
2224     	   pos >= sel->start && pos < sel->end) ||
2225     	  (sel->rectangular &&
2226     	   pos >= sel->start && lineStartPos <= sel->end &&
2227      	   dispIndex >= sel->rectStart && dispIndex < sel->rectEnd));
2228 }
2229 
2230 /*
2231 ** Translate window coordinates to the nearest (insert cursor or character
2232 ** cell) text position.  The parameter posType specifies how to interpret the
2233 ** position: CURSOR_POS means translate the coordinates to the nearest cursor
2234 ** position, and CHARACTER_POS means return the position of the character
2235 ** closest to (x, y).
2236 */
xyToPos(textDisp * textD,int x,int y,int posType)2237 static int xyToPos(textDisp *textD, int x, int y, int posType)
2238 {
2239     int charIndex, lineStart, lineLen, fontHeight;
2240     int charWidth, charLen, charStyle, visLineNum, xStep, outIndex;
2241     char *lineStr, expandedChar[MAX_EXP_CHAR_LEN];
2242 
2243     /* Find the visible line number corresponding to the y coordinate */
2244     fontHeight = textD->ascent + textD->descent;
2245     visLineNum = (y - textD->top) / fontHeight;
2246     if (visLineNum < 0)
2247 	return textD->firstChar;
2248     if (visLineNum >= textD->nVisibleLines)
2249 	visLineNum = textD->nVisibleLines - 1;
2250 
2251     /* Find the position at the start of the line */
2252     lineStart = textD->lineStarts[visLineNum];
2253 
2254     /* If the line start was empty, return the last position in the buffer */
2255     if (lineStart == -1)
2256     	return textD->buffer->length;
2257 
2258     /* Get the line text and its length */
2259     lineLen = visLineLength(textD, visLineNum);
2260     lineStr = BufGetRange(textD->buffer, lineStart, lineStart + lineLen);
2261 
2262     /* Step through character positions from the beginning of the line
2263        to find the character position corresponding to the x coordinate */
2264     xStep = textD->left - textD->horizOffset;
2265     outIndex = 0;
2266     for(charIndex=0; charIndex<lineLen; charIndex++) {
2267     	charLen = BufExpandCharacter(lineStr[charIndex], outIndex, expandedChar,
2268     		textD->buffer->tabDist, textD->buffer->nullSubsChar);
2269    	charStyle = styleOfPos(textD, lineStart, lineLen, charIndex, outIndex,
2270 				lineStr[charIndex]);
2271     	charWidth = stringWidth(textD, expandedChar, charLen, charStyle);
2272     	if (x < xStep + (posType == CURSOR_POS ? charWidth/2 : charWidth)) {
2273     	    NEditFree(lineStr);
2274     	    return lineStart + charIndex;
2275     	}
2276     	xStep += charWidth;
2277     	outIndex += charLen;
2278     }
2279 
2280     /* If the x position was beyond the end of the line, return the position
2281        of the newline at the end of the line */
2282     NEditFree(lineStr);
2283     return lineStart + lineLen;
2284 }
2285 
2286 /*
2287 ** Translate window coordinates to the nearest row and column number for
2288 ** positioning the cursor.  This, of course, makes no sense when the font is
2289 ** proportional, since there are no absolute columns.  The parameter posType
2290 ** specifies how to interpret the position: CURSOR_POS means translate the
2291 ** coordinates to the nearest position between characters, and CHARACTER_POS
2292 ** means translate the position to the nearest character cell.
2293 */
xyToUnconstrainedPos(textDisp * textD,int x,int y,int * row,int * column,int posType)2294 static void xyToUnconstrainedPos(textDisp *textD, int x, int y, int *row,
2295 	int *column, int posType)
2296 {
2297     int fontHeight = textD->ascent + textD->descent;
2298     int fontWidth = textD->fontStruct->max_bounds.width;
2299 
2300     /* Find the visible line number corresponding to the y coordinate */
2301     *row = (y - textD->top) / fontHeight;
2302     if (*row < 0) *row = 0;
2303     if (*row >= textD->nVisibleLines) *row = textD->nVisibleLines - 1;
2304     *column = ((x-textD->left) + textD->horizOffset +
2305     	    (posType == CURSOR_POS ? fontWidth/2 : 0)) / fontWidth;
2306     if (*column < 0) *column = 0;
2307 }
2308 
2309 /*
2310 ** Offset the line starts array, topLineNum, firstChar and lastChar, for a new
2311 ** vertical scroll position given by newTopLineNum.  If any currently displayed
2312 ** lines will still be visible, salvage the line starts values, otherwise,
2313 ** count lines from the nearest known line start (start or end of buffer, or
2314 ** the closest value in the lineStarts array)
2315 */
offsetLineStarts(textDisp * textD,int newTopLineNum)2316 static void offsetLineStarts(textDisp *textD, int newTopLineNum)
2317 {
2318     int oldTopLineNum = textD->topLineNum;
2319     int oldFirstChar = textD->firstChar;
2320     int lineDelta = newTopLineNum - oldTopLineNum;
2321     int nVisLines = textD->nVisibleLines;
2322     int *lineStarts = textD->lineStarts;
2323     int i, lastLineNum;
2324     textBuffer *buf = textD->buffer;
2325 
2326     /* If there was no offset, nothing needs to be changed */
2327     if (lineDelta == 0)
2328     	return;
2329 
2330     /* {   int i;
2331     	printf("Scroll, lineDelta %d\n", lineDelta);
2332     	printf("lineStarts Before: ");
2333     	for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2334     	printf("\n");
2335     } */
2336 
2337     /* Find the new value for firstChar by counting lines from the nearest
2338        known line start (start or end of buffer, or the closest value in the
2339        lineStarts array) */
2340     lastLineNum = oldTopLineNum + nVisLines - 1;
2341     if (newTopLineNum < oldTopLineNum && newTopLineNum < -lineDelta) {
2342     	textD->firstChar = TextDCountForwardNLines(textD, 0, newTopLineNum-1,
2343     	    	True);
2344     	/* printf("counting forward %d lines from start\n", newTopLineNum-1);*/
2345     } else if (newTopLineNum < oldTopLineNum) {
2346     	textD->firstChar = TextDCountBackwardNLines(textD, textD->firstChar,
2347     		-lineDelta);
2348     	/* printf("counting backward %d lines from firstChar\n", -lineDelta);*/
2349     } else if (newTopLineNum < lastLineNum) {
2350     	textD->firstChar = lineStarts[newTopLineNum - oldTopLineNum];
2351     	/* printf("taking new start from lineStarts[%d]\n",
2352     		newTopLineNum - oldTopLineNum); */
2353     } else if (newTopLineNum-lastLineNum < textD->nBufferLines-newTopLineNum) {
2354     	textD->firstChar = TextDCountForwardNLines(textD,
2355                 lineStarts[nVisLines-1], newTopLineNum - lastLineNum, True);
2356     	/* printf("counting forward %d lines from start of last line\n",
2357     		newTopLineNum - lastLineNum); */
2358     } else {
2359     	textD->firstChar = TextDCountBackwardNLines(textD, buf->length,
2360 		textD->nBufferLines - newTopLineNum + 1);
2361 	/* printf("counting backward %d lines from end\n",
2362     		textD->nBufferLines - newTopLineNum + 1); */
2363     }
2364 
2365     /* Fill in the line starts array */
2366     if (lineDelta < 0 && -lineDelta < nVisLines) {
2367     	for (i=nVisLines-1; i >= -lineDelta; i--)
2368     	    lineStarts[i] = lineStarts[i+lineDelta];
2369     	calcLineStarts(textD, 0, -lineDelta);
2370     } else if (lineDelta > 0 && lineDelta < nVisLines) {
2371     	for (i=0; i<nVisLines-lineDelta; i++)
2372     	    lineStarts[i] = lineStarts[i+lineDelta];
2373     	calcLineStarts(textD, nVisLines-lineDelta, nVisLines-1);
2374     } else
2375 	calcLineStarts(textD, 0, nVisLines);
2376 
2377     /* Set lastChar and topLineNum */
2378     calcLastChar(textD);
2379     textD->topLineNum = newTopLineNum;
2380 
2381     /* If we're numbering lines or being asked to maintain an absolute line
2382        number, re-calculate the absolute line number */
2383     offsetAbsLineNum(textD, oldFirstChar);
2384 
2385     /* {   int i;
2386     	printf("lineStarts After: ");
2387     	for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2388     	printf("\n");
2389     } */
2390 }
2391 
2392 /*
2393 ** Update the line starts array, topLineNum, firstChar and lastChar for text
2394 ** display "textD" after a modification to the text buffer, given by the
2395 ** position where the change began "pos", and the nmubers of characters
2396 ** and lines inserted and deleted.
2397 */
updateLineStarts(textDisp * textD,int pos,int charsInserted,int charsDeleted,int linesInserted,int linesDeleted,int * scrolled)2398 static void updateLineStarts(textDisp *textD, int pos, int charsInserted,
2399 	int charsDeleted, int linesInserted, int linesDeleted, int *scrolled)
2400 {
2401     int *lineStarts = textD->lineStarts;
2402     int i, lineOfPos, lineOfEnd, nVisLines = textD->nVisibleLines;
2403     int charDelta = charsInserted - charsDeleted;
2404     int lineDelta = linesInserted - linesDeleted;
2405 
2406     /* {   int i;
2407     	printf("linesDeleted %d, linesInserted %d, charsInserted %d, charsDeleted %d\n",
2408     	    	linesDeleted, linesInserted, charsInserted, charsDeleted);
2409     	printf("lineStarts Before: ");
2410     	for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2411     	printf("\n");
2412     } */
2413     /* If all of the changes were before the displayed text, the display
2414        doesn't change, just update the top line num and offset the line
2415        start entries and first and last characters */
2416     if (pos + charsDeleted < textD->firstChar) {
2417     	textD->topLineNum += lineDelta;
2418     	for (i=0; i<nVisLines && lineStarts[i] != -1; i++)
2419     	    lineStarts[i] += charDelta;
2420     	/* {   int i;
2421     	    printf("lineStarts after delete doesn't touch: ");
2422     	    for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2423     	    printf("\n");
2424     	} */
2425     	textD->firstChar += charDelta;
2426     	textD->lastChar += charDelta;
2427     	*scrolled = False;
2428     	return;
2429     }
2430 
2431     /* The change began before the beginning of the displayed text, but
2432        part or all of the displayed text was deleted */
2433     if (pos < textD->firstChar) {
2434     	/* If some text remains in the window, anchor on that  */
2435     	if (posToVisibleLineNum(textD, pos + charsDeleted, &lineOfEnd) &&
2436     		++lineOfEnd < nVisLines && lineStarts[lineOfEnd] != -1) {
2437     	    textD->topLineNum = max(1, textD->topLineNum + lineDelta);
2438     	    textD->firstChar = TextDCountBackwardNLines(textD,
2439     	    	    lineStarts[lineOfEnd] + charDelta, lineOfEnd);
2440     	/* Otherwise anchor on original line number and recount everything */
2441     	} else {
2442     	    if (textD->topLineNum > textD->nBufferLines + lineDelta) {
2443     	    	textD->topLineNum = 1;
2444     	    	textD->firstChar = 0;
2445     	    } else
2446     		textD->firstChar = TextDCountForwardNLines(textD, 0,
2447     	    		textD->topLineNum - 1, True);
2448     	}
2449     	calcLineStarts(textD, 0, nVisLines-1);
2450     	/* {   int i;
2451     	    printf("lineStarts after delete encroaches: ");
2452     	    for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2453     	    printf("\n");
2454     	} */
2455     	/* calculate lastChar by finding the end of the last displayed line */
2456     	calcLastChar(textD);
2457     	*scrolled = True;
2458     	return;
2459     }
2460 
2461     /* If the change was in the middle of the displayed text (it usually is),
2462        salvage as much of the line starts array as possible by moving and
2463        offsetting the entries after the changed area, and re-counting the
2464        added lines or the lines beyond the salvaged part of the line starts
2465        array */
2466     if (pos <= textD->lastChar) {
2467     	/* find line on which the change began */
2468     	posToVisibleLineNum(textD, pos, &lineOfPos);
2469     	/* salvage line starts after the changed area */
2470     	if (lineDelta == 0) {
2471     	    for (i=lineOfPos+1; i<nVisLines && lineStarts[i]!= -1; i++)
2472     		lineStarts[i] += charDelta;
2473     	} else if (lineDelta > 0) {
2474     	    for (i=nVisLines-1; i>=lineOfPos+lineDelta+1; i--)
2475     		lineStarts[i] = lineStarts[i-lineDelta] +
2476     			(lineStarts[i-lineDelta] == -1 ? 0 : charDelta);
2477     	} else /* (lineDelta < 0) */ {
2478     	    for (i=max(0,lineOfPos+1); i<nVisLines+lineDelta; i++)
2479     	    	lineStarts[i] = lineStarts[i-lineDelta] +
2480     	    		(lineStarts[i-lineDelta] == -1 ? 0 : charDelta);
2481     	}
2482     	/* {   int i;
2483     	    printf("lineStarts after salvage: ");
2484     	    for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2485     	    printf("\n");
2486     	} */
2487     	/* fill in the missing line starts */
2488     	if (linesInserted >= 0)
2489     	    calcLineStarts(textD, lineOfPos + 1, lineOfPos + linesInserted);
2490     	if (lineDelta < 0)
2491     	    calcLineStarts(textD, nVisLines+lineDelta, nVisLines);
2492     	/* {   int i;
2493     	    printf("lineStarts after recalculation: ");
2494     	    for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2495     	    printf("\n");
2496     	} */
2497     	/* calculate lastChar by finding the end of the last displayed line */
2498     	calcLastChar(textD);
2499     	*scrolled = False;
2500     	return;
2501     }
2502 
2503     /* Change was past the end of the displayed text, but displayable by virtue
2504        of being an insert at the end of the buffer into visible blank lines */
2505     if (emptyLinesVisible(textD)) {
2506     	posToVisibleLineNum(textD, pos, &lineOfPos);
2507     	calcLineStarts(textD, lineOfPos, lineOfPos+linesInserted);
2508     	calcLastChar(textD);
2509     	/* {   int i;
2510     	    printf("lineStarts after insert at end: ");
2511     	    for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]);
2512     	    printf("\n");
2513     	} */
2514     	*scrolled = False;
2515     	return;
2516     }
2517 
2518     /* Change was beyond the end of the buffer and not visible, do nothing */
2519     *scrolled = False;
2520 }
2521 
2522 /*
2523 ** Scan through the text in the "textD"'s buffer and recalculate the line
2524 ** starts array values beginning at index "startLine" and continuing through
2525 ** (including) "endLine".  It assumes that the line starts entry preceding
2526 ** "startLine" (or textD->firstChar if startLine is 0) is good, and re-counts
2527 ** newlines to fill in the requested entries.  Out of range values for
2528 ** "startLine" and "endLine" are acceptable.
2529 */
calcLineStarts(textDisp * textD,int startLine,int endLine)2530 static void calcLineStarts(textDisp *textD, int startLine, int endLine)
2531 {
2532     int startPos, bufLen = textD->buffer->length;
2533     int line, lineEnd, nextLineStart, nVis = textD->nVisibleLines;
2534     int *lineStarts = textD->lineStarts;
2535 
2536     /* Clean up (possibly) messy input parameters */
2537     if (nVis == 0) return;
2538     if (endLine < 0) endLine = 0;
2539     if (endLine >= nVis) endLine = nVis - 1;
2540     if (startLine < 0) startLine = 0;
2541     if (startLine >=nVis) startLine = nVis - 1;
2542     if (startLine > endLine)
2543     	return;
2544 
2545     /* Find the last known good line number -> position mapping */
2546     if (startLine == 0) {
2547     	lineStarts[0] = textD->firstChar;
2548     	startLine = 1;
2549     }
2550     startPos = lineStarts[startLine-1];
2551 
2552     /* If the starting position is already past the end of the text,
2553        fill in -1's (means no text on line) and return */
2554     if (startPos == -1) {
2555         for (line=startLine; line<=endLine; line++)
2556     	    lineStarts[line] = -1;
2557     	return;
2558     }
2559 
2560     /* Loop searching for ends of lines and storing the positions of the
2561        start of the next line in lineStarts */
2562     for (line=startLine; line<=endLine; line++) {
2563     	findLineEnd(textD, startPos, True, &lineEnd, &nextLineStart);
2564     	startPos = nextLineStart;
2565     	if (startPos >= bufLen) {
2566     	    /* If the buffer ends with a newline or line break, put
2567     	       buf->length in the next line start position (instead of
2568     	       a -1 which is the normal marker for an empty line) to
2569     	       indicate that the cursor may safely be displayed there */
2570     	    if (line == 0 || (lineStarts[line-1] != bufLen &&
2571     	    	    lineEnd != nextLineStart)) {
2572     	    	lineStarts[line] = bufLen;
2573     	    	line++;
2574     	    }
2575     	    break;
2576     	}
2577     	lineStarts[line] = startPos;
2578     }
2579 
2580     /* Set any entries beyond the end of the text to -1 */
2581     for (; line<=endLine; line++)
2582     	lineStarts[line] = -1;
2583 }
2584 
2585 /*
2586 ** Given a textDisp with a complete, up-to-date lineStarts array, update
2587 ** the lastChar entry to point to the last buffer position displayed.
2588 */
calcLastChar(textDisp * textD)2589 static void calcLastChar(textDisp *textD)
2590 {
2591     int i;
2592 
2593     for (i=textD->nVisibleLines-1; i>0 && textD->lineStarts[i]== -1; i--);
2594     textD->lastChar = i < 0 ? 0 :
2595     	    TextDEndOfLine(textD, textD->lineStarts[i], True);
2596 }
2597 
TextDImposeGraphicsExposeTranslation(textDisp * textD,int * xOffset,int * yOffset)2598 void TextDImposeGraphicsExposeTranslation(textDisp *textD, int *xOffset, int *yOffset)
2599 {
2600     if (textD->graphicsExposeQueue) {
2601         graphicExposeTranslationEntry *thisGEQEntry = textD->graphicsExposeQueue->next;
2602         if (thisGEQEntry) {
2603             *xOffset += thisGEQEntry->horizontal;
2604             *yOffset += thisGEQEntry->vertical;
2605         }
2606     }
2607 }
2608 
TextDPopGraphicExposeQueueEntry(textDisp * textD)2609 Boolean TextDPopGraphicExposeQueueEntry(textDisp *textD)
2610 {
2611     graphicExposeTranslationEntry *removedGEQEntry = textD->graphicsExposeQueue;
2612 
2613     if (removedGEQEntry) {
2614         textD->graphicsExposeQueue = removedGEQEntry->next;
2615         NEditFree(removedGEQEntry);
2616     }
2617     return(removedGEQEntry?True:False);
2618 }
2619 
TextDTranlateGraphicExposeQueue(textDisp * textD,int xOffset,int yOffset,Boolean appendEntry)2620 void TextDTranlateGraphicExposeQueue(textDisp *textD, int xOffset, int yOffset, Boolean appendEntry)
2621 {
2622     graphicExposeTranslationEntry *newGEQEntry = NULL;
2623     if (appendEntry) {
2624         newGEQEntry = (graphicExposeTranslationEntry *)NEditMalloc(sizeof(graphicExposeTranslationEntry));
2625         newGEQEntry->next = NULL;
2626         newGEQEntry->horizontal = xOffset;
2627         newGEQEntry->vertical = yOffset;
2628     }
2629     if (textD->graphicsExposeQueue) {
2630         graphicExposeTranslationEntry *iter = textD->graphicsExposeQueue;
2631         while (iter->next) {
2632             iter->next->horizontal += xOffset;
2633             iter->next->vertical += yOffset;
2634             iter = iter->next;
2635         }
2636         if (appendEntry) {
2637             iter->next = (struct graphicExposeTranslationEntry *)newGEQEntry;
2638         }
2639     }
2640     else {
2641         if (appendEntry) {
2642             textD->graphicsExposeQueue = newGEQEntry;
2643         }
2644     }
2645 }
2646 
setScroll(textDisp * textD,int topLineNum,int horizOffset,int updateVScrollBar,int updateHScrollBar)2647 static void setScroll(textDisp *textD, int topLineNum, int horizOffset,
2648         int updateVScrollBar, int updateHScrollBar)
2649 {
2650     int fontHeight = textD->ascent + textD->descent;
2651     int origHOffset = textD->horizOffset;
2652     int lineDelta = textD->topLineNum - topLineNum;
2653     int xOffset, yOffset, srcX, srcY, dstX, dstY, width, height;
2654     int exactHeight = textD->height - textD->height %
2655             (textD->ascent + textD->descent);
2656 
2657     /* Do nothing if scroll position hasn't actually changed or there's no
2658        window to draw in yet */
2659     if (XtWindow(textD->w) == 0 ||  (textD->horizOffset == horizOffset &&
2660             textD->topLineNum == topLineNum))
2661         return;
2662 
2663     /* If part of the cursor is protruding beyond the text clipping region,
2664        clear it off */
2665     blankCursorProtrusions(textD);
2666 
2667     /* If the vertical scroll position has changed, update the line
2668        starts array and related counters in the text display */
2669     offsetLineStarts(textD, topLineNum);
2670 
2671     /* Just setting textD->horizOffset is enough information for redisplay */
2672     textD->horizOffset = horizOffset;
2673 
2674     /* Update the scroll bar positions if requested, note: updating the
2675        horizontal scroll bars can have the further side-effect of changing
2676        the horizontal scroll position, textD->horizOffset */
2677     if (updateVScrollBar && textD->vScrollBar != NULL) {
2678         updateVScrollBarRange(textD);
2679     }
2680     if (updateHScrollBar && textD->hScrollBar != NULL) {
2681         updateHScrollBarRange(textD);
2682     }
2683 
2684     /* Redisplay everything if the window is partially obscured (since
2685        it's too hard to tell what displayed areas are salvageable) or
2686        if there's nothing to recover because the scroll distance is large */
2687     xOffset = origHOffset - textD->horizOffset;
2688     yOffset = lineDelta * fontHeight;
2689     if (textD->visibility != VisibilityUnobscured ||
2690             abs(xOffset) > textD->width || abs(yOffset) > exactHeight) {
2691         TextDTranlateGraphicExposeQueue(textD, xOffset, yOffset, False);
2692         TextDRedisplayRect(textD, textD->left, textD->top, textD->width,
2693                 textD->height);
2694     } else {
2695         /* If the window is not obscured, paint most of the window using XCopyArea
2696            from existing displayed text, and redraw only what's necessary */
2697         /* Recover the useable window areas by moving to the proper location */
2698         srcX = textD->left + (xOffset >= 0 ? 0 : -xOffset);
2699         dstX = textD->left + (xOffset >= 0 ? xOffset : 0);
2700         width = textD->width - abs(xOffset);
2701         srcY = textD->top + (yOffset >= 0 ? 0 : -yOffset);
2702         dstY = textD->top + (yOffset >= 0 ? yOffset : 0);
2703         height = exactHeight - abs(yOffset);
2704         resetClipRectangles(textD);
2705         TextDTranlateGraphicExposeQueue(textD, xOffset, yOffset, True);
2706         XCopyArea(XtDisplay(textD->w), XtWindow(textD->w), XtWindow(textD->w),
2707                 textD->gc, srcX, srcY, width, height, dstX, dstY);
2708         /* redraw the un-recoverable parts */
2709         if (yOffset > 0) {
2710             TextDRedisplayRect(textD, textD->left, textD->top,
2711                     textD->width, yOffset);
2712         }
2713         else if (yOffset < 0) {
2714             TextDRedisplayRect(textD, textD->left, textD->top +
2715                     textD->height + yOffset, textD->width, -yOffset);
2716         }
2717         if (xOffset > 0) {
2718             TextDRedisplayRect(textD, textD->left, textD->top,
2719                     xOffset, textD->height);
2720         }
2721         else if (xOffset < 0) {
2722             TextDRedisplayRect(textD, textD->left + textD->width + xOffset,
2723                     textD->top, -xOffset, textD->height);
2724         }
2725         /* Restore protruding parts of the cursor */
2726         textDRedisplayRange(textD, textD->cursorPos-1, textD->cursorPos+1);
2727     }
2728 
2729     /* Refresh line number/calltip display if its up and we've scrolled
2730         vertically */
2731     if (lineDelta != 0) {
2732         redrawLineNumbers(textD, False);
2733         TextDRedrawCalltip(textD, 0);
2734     }
2735 
2736     HandleAllPendingGraphicsExposeNoExposeEvents((TextWidget)textD->w, NULL);
2737 }
2738 
2739 /*
2740 ** Update the minimum, maximum, slider size, page increment, and value
2741 ** for vertical scroll bar.
2742 */
updateVScrollBarRange(textDisp * textD)2743 static void updateVScrollBarRange(textDisp *textD)
2744 {
2745     int sliderSize, sliderMax, sliderValue;
2746 
2747     if (textD->vScrollBar == NULL)
2748         return;
2749 
2750     /* The Vert. scroll bar value and slider size directly represent the top
2751        line number, and the number of visible lines respectively.  The scroll
2752        bar maximum value is chosen to generally represent the size of the whole
2753        buffer, with minor adjustments to keep the scroll bar widget happy */
2754     sliderSize = max(textD->nVisibleLines, 1); /* Avoid X warning (size < 1) */
2755     sliderValue = textD->topLineNum;
2756     sliderMax = max(textD->nBufferLines + 2 +
2757                     TEXT_OF_TEXTD(textD).cursorVPadding,
2758                     sliderSize + sliderValue);
2759     XtVaSetValues(textD->vScrollBar,
2760             XmNmaximum, sliderMax,
2761             XmNsliderSize, sliderSize,
2762             XmNpageIncrement, max(1, textD->nVisibleLines - 1),
2763             XmNvalue, sliderValue, NULL);
2764 }
2765 
2766 /*
2767 ** Update the minimum, maximum, slider size, page increment, and value
2768 ** for the horizontal scroll bar.  If scroll position is such that there
2769 ** is blank space to the right of all lines of text, scroll back (adjust
2770 ** horizOffset but don't redraw) to take up the slack and position the
2771 ** right edge of the text at the right edge of the display.
2772 **
2773 ** Note, there is some cost to this routine, since it scans the whole range
2774 ** of displayed text, particularly since it's usually called for each typed
2775 ** character!
2776 */
updateHScrollBarRange(textDisp * textD)2777 static int updateHScrollBarRange(textDisp *textD)
2778 {
2779     int i, maxWidth = 0, sliderMax, sliderWidth;
2780     int origHOffset = textD->horizOffset;
2781 
2782     if (textD->hScrollBar == NULL || !XtIsManaged(textD->hScrollBar))
2783     	return False;
2784 
2785     /* Scan all the displayed lines to find the width of the longest line */
2786     for (i=0; i<textD->nVisibleLines && textD->lineStarts[i]!= -1; i++)
2787     	maxWidth = max(measureVisLine(textD, i), maxWidth);
2788 
2789     /* If the scroll position is beyond what's necessary to keep all lines
2790        in view, scroll to the left to bring the end of the longest line to
2791        the right margin */
2792     if (maxWidth < textD->width + textD->horizOffset && textD->horizOffset > 0)
2793     	textD->horizOffset = max(0, maxWidth - textD->width);
2794 
2795     /* Readjust the scroll bar */
2796     sliderWidth = textD->width;
2797     sliderMax = max(maxWidth, sliderWidth + textD->horizOffset);
2798     XtVaSetValues(textD->hScrollBar,
2799     	    XmNmaximum, sliderMax,
2800     	    XmNsliderSize, sliderWidth,
2801     	    XmNpageIncrement, max(textD->width - 100, 10),
2802     	    XmNvalue, textD->horizOffset, NULL);
2803 
2804     /* Return True if scroll position was changed */
2805     return origHOffset != textD->horizOffset;
2806 }
2807 
2808 /*
2809 ** Define area for drawing line numbers.  A width of 0 disables line
2810 ** number drawing.
2811 */
TextDSetLineNumberArea(textDisp * textD,int lineNumLeft,int lineNumWidth,int textLeft)2812 void TextDSetLineNumberArea(textDisp *textD, int lineNumLeft, int lineNumWidth,
2813 	int textLeft)
2814 {
2815     int newWidth = textD->width + textD->left - textLeft;
2816     textD->lineNumLeft = lineNumLeft;
2817     textD->lineNumWidth = lineNumWidth;
2818     textD->left = textLeft;
2819     XClearWindow(XtDisplay(textD->w), XtWindow(textD->w));
2820     resetAbsLineNum(textD);
2821     TextDResize(textD, newWidth, textD->height);
2822     TextDRedisplayRect(textD, 0, textD->top, INT_MAX, textD->height);
2823 }
2824 
2825 /*
2826 ** Refresh the line number area.  If clearAll is False, writes only over
2827 ** the character cell areas.  Setting clearAll to True will clear out any
2828 ** stray marks outside of the character cell area, which might have been
2829 ** left from before a resize or font change.
2830 */
redrawLineNumbers(textDisp * textD,int clearAll)2831 static void redrawLineNumbers(textDisp *textD, int clearAll)
2832 {
2833     int y, line, visLine, nCols, lineStart;
2834     char lineNumString[12];
2835     int lineHeight = textD->ascent + textD->descent;
2836     int charWidth = textD->fontStruct->max_bounds.width;
2837     XRectangle clipRect;
2838     Display *display = XtDisplay(textD->w);
2839 
2840     /* Don't draw if lineNumWidth == 0 (line numbers are hidden), or widget is
2841        not yet realized */
2842     if (textD->lineNumWidth == 0 || XtWindow(textD->w) == 0)
2843         return;
2844 
2845     /* Make sure we reset the clipping range for the line numbers GC, because
2846        the GC may be shared (eg, if the line numbers and text have the same
2847        color) and therefore the clipping ranges may be invalid. */
2848     clipRect.x = textD->lineNumLeft;
2849     clipRect.y = textD->top;
2850     clipRect.width = textD->lineNumWidth;
2851     clipRect.height = textD->height;
2852     XSetClipRectangles(display, textD->lineNumGC, 0, 0,
2853     	    &clipRect, 1, Unsorted);
2854 
2855     /* Erase the previous contents of the line number area, if requested */
2856     if (clearAll)
2857         XClearArea(XtDisplay(textD->w), XtWindow(textD->w), textD->lineNumLeft,
2858                 textD->top, textD->lineNumWidth, textD->height, False);
2859 
2860     /* Draw the line numbers, aligned to the text */
2861     nCols = min(11, textD->lineNumWidth / charWidth);
2862     y = textD->top;
2863     line = getAbsTopLineNum(textD);
2864     for (visLine=0; visLine < textD->nVisibleLines; visLine++) {
2865         lineStart = textD->lineStarts[visLine];
2866         if (lineStart != -1 && (lineStart==0 ||
2867                 BufGetCharacter(textD->buffer, lineStart-1)=='\n')) {
2868             sprintf(lineNumString, "%*d", nCols, line);
2869             XDrawImageString(XtDisplay(textD->w), XtWindow(textD->w),
2870                     textD->lineNumGC, textD->lineNumLeft, y + textD->ascent,
2871                     lineNumString, strlen(lineNumString));
2872             line++;
2873         } else {
2874             XClearArea(XtDisplay(textD->w), XtWindow(textD->w),
2875                     textD->lineNumLeft, y, textD->lineNumWidth,
2876                     textD->ascent + textD->descent, False);
2877             if (visLine == 0)
2878                 line++;
2879         }
2880         y += lineHeight;
2881     }
2882 }
2883 
2884 /*
2885 ** Callbacks for drag or valueChanged on scroll bars
2886 */
vScrollCB(Widget w,XtPointer clientData,XtPointer callData)2887 static void vScrollCB(Widget w, XtPointer clientData, XtPointer callData)
2888 {
2889     textDisp *textD = (textDisp *)clientData;
2890     int newValue = ((XmScrollBarCallbackStruct *)callData)->value;
2891     int lineDelta = newValue - textD->topLineNum;
2892 
2893     if (lineDelta == 0)
2894         return;
2895     setScroll(textD, newValue, textD->horizOffset, False, True);
2896 }
hScrollCB(Widget w,XtPointer clientData,XtPointer callData)2897 static void hScrollCB(Widget w, XtPointer clientData, XtPointer callData)
2898 {
2899     textDisp *textD = (textDisp *)clientData;
2900     int newValue = ((XmScrollBarCallbackStruct *)callData)->value;
2901 
2902     if (newValue == textD->horizOffset)
2903         return;
2904     setScroll(textD, textD->topLineNum, newValue, False, False);
2905 }
2906 
visibilityEH(Widget w,XtPointer data,XEvent * event,Boolean * continueDispatch)2907 static void visibilityEH(Widget w, XtPointer data, XEvent *event,
2908         Boolean *continueDispatch)
2909 {
2910     /* Record whether the window is fully visible or not.  This information
2911        is used for choosing the scrolling methodology for optimal performance,
2912        if the window is partially obscured, XCopyArea may not work */
2913     ((textDisp *)data)->visibility = ((XVisibilityEvent *)event)->state;
2914 }
2915 
max(int i1,int i2)2916 static int max(int i1, int i2)
2917 {
2918     return i1 >= i2 ? i1 : i2;
2919 }
2920 
min(int i1,int i2)2921 static int min(int i1, int i2)
2922 {
2923     return i1 <= i2 ? i1 : i2;
2924 }
2925 
2926 /*
2927 ** Count the number of newlines in a null-terminated text string;
2928 */
countLines(const char * string)2929 static int countLines(const char *string)
2930 {
2931     const char *c;
2932     int lineCount = 0;
2933 
2934     if (string == NULL)
2935 	return 0;
2936     for (c=string; *c!='\0'; c++)
2937     	if (*c == '\n') lineCount++;
2938     return lineCount;
2939 }
2940 
2941 /*
2942 ** Return the width in pixels of the displayed line pointed to by "visLineNum"
2943 */
measureVisLine(textDisp * textD,int visLineNum)2944 static int measureVisLine(textDisp *textD, int visLineNum)
2945 {
2946     int i, width = 0, len, style, lineLen = visLineLength(textD, visLineNum);
2947     int charCount = 0, lineStartPos = textD->lineStarts[visLineNum];
2948     char expandedChar[MAX_EXP_CHAR_LEN];
2949 
2950     if (textD->styleBuffer == NULL) {
2951 	for (i=0; i<lineLen; i++) {
2952     	    len = BufGetExpandedChar(textD->buffer, lineStartPos + i,
2953     		    charCount, expandedChar);
2954     	    width += XTextWidth(textD->fontStruct, expandedChar, len);
2955     	    charCount += len;
2956 	}
2957     } else {
2958     	for (i=0; i<lineLen; i++) {
2959     	    len = BufGetExpandedChar(textD->buffer, lineStartPos+i,
2960     		    charCount, expandedChar);
2961     	    style = (unsigned char)BufGetCharacter(textD->styleBuffer,
2962 		    lineStartPos+i) - ASCII_A;
2963     	    width += XTextWidth(textD->styleTable[style].font, expandedChar,
2964     	    	    len);
2965     	    charCount += len;
2966 	}
2967     }
2968     return width;
2969 }
2970 
2971 /*
2972 ** Return true if there are lines visible with no corresponding buffer text
2973 */
emptyLinesVisible(textDisp * textD)2974 static int emptyLinesVisible(textDisp *textD)
2975 {
2976     return textD->nVisibleLines > 0 &&
2977     	    textD->lineStarts[textD->nVisibleLines-1] == -1;
2978 }
2979 
2980 /*
2981 ** When the cursor is at the left or right edge of the text, part of it
2982 ** sticks off into the clipped region beyond the text.  Normal redrawing
2983 ** can not overwrite this protruding part of the cursor, so it must be
2984 ** erased independently by calling this routine.
2985 */
blankCursorProtrusions(textDisp * textD)2986 static void blankCursorProtrusions(textDisp *textD)
2987 {
2988     int x, width, cursorX = textD->cursorX, cursorY = textD->cursorY;
2989     int fontWidth = textD->fontStruct->max_bounds.width;
2990     int fontHeight = textD->ascent + textD->descent;
2991     int cursorWidth, left = textD->left, right = left + textD->width;
2992 
2993     cursorWidth = (fontWidth/3) * 2;
2994     if (cursorX >= left-1 && cursorX <= left + cursorWidth/2 - 1) {
2995         x = cursorX - cursorWidth/2;
2996         width = left - x;
2997     } else if (cursorX >= right - cursorWidth/2 && cursorX <= right) {
2998         x = right;
2999         width = cursorX + cursorWidth/2 + 2 - right;
3000     } else
3001         return;
3002 
3003     XClearArea(XtDisplay(textD->w), XtWindow(textD->w), x, cursorY,
3004             width, fontHeight, False);
3005 }
3006 
3007 /*
3008 ** Allocate shared graphics contexts used by the widget, which must be
3009 ** re-allocated on a font change.
3010 */
allocateFixedFontGCs(textDisp * textD,XFontStruct * fontStruct,Pixel bgPixel,Pixel fgPixel,Pixel selectFGPixel,Pixel selectBGPixel,Pixel highlightFGPixel,Pixel highlightBGPixel,Pixel lineNumFGPixel)3011 static void allocateFixedFontGCs(textDisp *textD, XFontStruct *fontStruct,
3012         Pixel bgPixel, Pixel fgPixel, Pixel selectFGPixel, Pixel selectBGPixel,
3013         Pixel highlightFGPixel, Pixel highlightBGPixel, Pixel lineNumFGPixel)
3014 {
3015     textD->gc = allocateGC(textD->w, GCFont | GCForeground | GCBackground,
3016             fgPixel, bgPixel, fontStruct->fid, GCClipMask, GCArcMode);
3017     textD->selectGC = allocateGC(textD->w, GCFont | GCForeground | GCBackground,
3018             selectFGPixel, selectBGPixel, fontStruct->fid, GCClipMask,
3019             GCArcMode);
3020     textD->selectBGGC = allocateGC(textD->w, GCForeground, selectBGPixel, 0,
3021             fontStruct->fid, GCClipMask, GCArcMode);
3022     textD->highlightGC = allocateGC(textD->w, GCFont|GCForeground|GCBackground,
3023             highlightFGPixel, highlightBGPixel, fontStruct->fid, GCClipMask,
3024             GCArcMode);
3025     textD->highlightBGGC = allocateGC(textD->w, GCForeground, highlightBGPixel,
3026             0, fontStruct->fid, GCClipMask, GCArcMode);
3027     textD->lineNumGC = allocateGC(textD->w, GCFont | GCForeground |
3028             GCBackground, lineNumFGPixel, bgPixel, fontStruct->fid,
3029             GCClipMask, GCArcMode);
3030 }
3031 
3032 /*
3033 ** X11R4 does not have the XtAllocateGC function for sharing graphics contexts
3034 ** with changeable fields.  Unfortunately the R4 call for creating shared
3035 ** graphics contexts (XtGetGC) is rarely useful because most widgets need
3036 ** to be able to set and change clipping, and that makes the GC unshareable.
3037 **
3038 ** This function allocates and returns a gc, using XtAllocateGC if possible,
3039 ** or XCreateGC on X11R4 systems where XtAllocateGC is not available.
3040 */
allocateGC(Widget w,unsigned long valueMask,unsigned long foreground,unsigned long background,Font font,unsigned long dynamicMask,unsigned long dontCareMask)3041 static GC allocateGC(Widget w, unsigned long valueMask,
3042 	unsigned long foreground, unsigned long background, Font font,
3043 	unsigned long dynamicMask, unsigned long dontCareMask)
3044 {
3045     XGCValues gcValues;
3046 
3047     gcValues.font = font;
3048     gcValues.background = background;
3049     gcValues.foreground = foreground;
3050 #if defined(XlibSpecificationRelease) && XlibSpecificationRelease > 4
3051     return XtAllocateGC(w, 0, valueMask, &gcValues, dynamicMask,
3052     	    dontCareMask);
3053 #else
3054     return XCreateGC(XtDisplay(w), RootWindowOfScreen(XtScreen(w)),
3055     	    valueMask, &gcValues);
3056 #endif
3057 }
3058 
3059 /*
3060 ** Release a gc allocated with allocateGC above
3061 */
releaseGC(Widget w,GC gc)3062 static void releaseGC(Widget w, GC gc)
3063 {
3064 #if defined(XlibSpecificationRelease) && XlibSpecificationRelease > 4
3065     XtReleaseGC(w, gc);
3066 #else
3067     XFreeGC(XtDisplay(w), gc);
3068 #endif
3069 }
3070 
3071 /*
3072 ** resetClipRectangles sets the clipping rectangles for GCs which clip
3073 ** at the text boundary (as opposed to the window boundary).  These GCs
3074 ** are shared such that the drawing styles are constant, but the clipping
3075 ** rectangles are allowed to change among different users of the GCs (the
3076 ** GCs were created with XtAllocGC).  This routine resets them so the clipping
3077 ** rectangles are correct for this text display.
3078 */
resetClipRectangles(textDisp * textD)3079 static void resetClipRectangles(textDisp *textD)
3080 {
3081     XRectangle clipRect;
3082     Display *display = XtDisplay(textD->w);
3083 
3084     clipRect.x = textD->left;
3085     clipRect.y = textD->top;
3086     clipRect.width = textD->width;
3087     clipRect.height = textD->height - textD->height %
3088     	    (textD->ascent + textD->descent);
3089 
3090     XSetClipRectangles(display, textD->gc, 0, 0,
3091     	    &clipRect, 1, Unsorted);
3092     XSetClipRectangles(display, textD->selectGC, 0, 0,
3093             &clipRect, 1, Unsorted);
3094     XSetClipRectangles(display, textD->highlightGC, 0, 0,
3095             &clipRect, 1, Unsorted);
3096     XSetClipRectangles(display, textD->selectBGGC, 0, 0,
3097             &clipRect, 1, Unsorted);
3098     XSetClipRectangles(display, textD->highlightBGGC, 0, 0,
3099             &clipRect, 1, Unsorted);
3100     XSetClipRectangles(display, textD->styleGC, 0, 0,
3101             &clipRect, 1, Unsorted);
3102 }
3103 
3104 /*
3105 ** Return the length of a line (number of displayable characters) by examining
3106 ** entries in the line starts array rather than by scanning for newlines
3107 */
visLineLength(textDisp * textD,int visLineNum)3108 static int visLineLength(textDisp *textD, int visLineNum)
3109 {
3110     int nextLineStart, lineStartPos = textD->lineStarts[visLineNum];
3111 
3112     if (lineStartPos == -1)
3113     	return 0;
3114     if (visLineNum+1 >= textD->nVisibleLines)
3115     	return textD->lastChar - lineStartPos;
3116     nextLineStart = textD->lineStarts[visLineNum+1];
3117     if (nextLineStart == -1)
3118 	return textD->lastChar - lineStartPos;
3119     if (wrapUsesCharacter(textD, nextLineStart-1))
3120     	return nextLineStart-1 - lineStartPos;
3121     return nextLineStart - lineStartPos;
3122 }
3123 
3124 /*
3125 ** When continuous wrap is on, and the user inserts or deletes characters,
3126 ** wrapping can happen before and beyond the changed position.  This routine
3127 ** finds the extent of the changes, and counts the deleted and inserted lines
3128 ** over that range.  It also attempts to minimize the size of the range to
3129 ** what has to be counted and re-displayed, so the results can be useful
3130 ** both for delimiting where the line starts need to be recalculated, and
3131 ** for deciding what part of the text to redisplay.
3132 */
findWrapRange(textDisp * textD,const char * deletedText,int pos,int nInserted,int nDeleted,int * modRangeStart,int * modRangeEnd,int * linesInserted,int * linesDeleted)3133 static void findWrapRange(textDisp *textD, const char *deletedText, int pos,
3134     	int nInserted, int nDeleted, int *modRangeStart, int *modRangeEnd,
3135     	int *linesInserted, int *linesDeleted)
3136 {
3137     int length, retPos, retLines, retLineStart, retLineEnd;
3138     textBuffer *deletedTextBuf, *buf = textD->buffer;
3139     int nVisLines = textD->nVisibleLines;
3140     int *lineStarts = textD->lineStarts;
3141     int countFrom, countTo, lineStart, adjLineStart, i;
3142     int visLineNum = 0, nLines = 0;
3143 
3144     /*
3145     ** Determine where to begin searching: either the previous newline, or
3146     ** if possible, limit to the start of the (original) previous displayed
3147     ** line, using information from the existing line starts array
3148     */
3149     if (pos >= textD->firstChar && pos <= textD->lastChar) {
3150     	for (i=nVisLines-1; i>0; i--)
3151     	    if (lineStarts[i] != -1 && pos >= lineStarts[i])
3152     		break;
3153     	if (i > 0) {
3154     	    countFrom = lineStarts[i-1];
3155     	    visLineNum = i-1;
3156     	} else
3157     	    countFrom = BufStartOfLine(buf, pos);
3158     } else
3159     	countFrom = BufStartOfLine(buf, pos);
3160 
3161 
3162     /*
3163     ** Move forward through the (new) text one line at a time, counting
3164     ** displayed lines, and looking for either a real newline, or for the
3165     ** line starts to re-sync with the original line starts array
3166     */
3167     lineStart = countFrom;
3168     *modRangeStart = countFrom;
3169     while (True) {
3170 
3171     	/* advance to the next line.  If the line ended in a real newline
3172     	   or the end of the buffer, that's far enough */
3173     	wrappedLineCounter(textD, buf, lineStart, buf->length, 1, True, 0,
3174     	    	&retPos, &retLines, &retLineStart, &retLineEnd);
3175     	if (retPos >= buf->length) {
3176     	    countTo = buf->length;
3177     	    *modRangeEnd = countTo;
3178     	    if (retPos != retLineEnd)
3179     	    	nLines++;
3180     	    break;
3181     	} else
3182     	    lineStart = retPos;
3183     	nLines++;
3184     	if (lineStart > pos + nInserted &&
3185     	    	BufGetCharacter(buf, lineStart-1) == '\n') {
3186     	    countTo = lineStart;
3187     	    *modRangeEnd = lineStart;
3188     	    break;
3189     	}
3190 
3191 	/* Don't try to resync in continuous wrap mode with non-fixed font
3192 	   sizes; it would result in a chicken-and-egg dependency between
3193 	   the calculations for the inserted and the deleted lines.
3194            If we're in that mode, the number of deleted lines is calculated in
3195            advance, without resynchronization, so we shouldn't resynchronize
3196            for the inserted lines either. */
3197 	if (textD->suppressResync)
3198 	    continue;
3199 
3200     	/* check for synchronization with the original line starts array
3201     	   before pos, if so, the modified range can begin later */
3202      	if (lineStart <= pos) {
3203     	    while (visLineNum<nVisLines && lineStarts[visLineNum] < lineStart)
3204     		visLineNum++;
3205      	    if (visLineNum < nVisLines && lineStarts[visLineNum] == lineStart) {
3206     		countFrom = lineStart;
3207     		nLines = 0;
3208     		if (visLineNum+1 < nVisLines && lineStarts[visLineNum+1] != -1)
3209     		    *modRangeStart = min(pos, lineStarts[visLineNum+1]-1);
3210     		else
3211     		    *modRangeStart = countFrom;
3212     	    } else
3213     	    	*modRangeStart = min(*modRangeStart, lineStart-1);
3214     	}
3215 
3216    	/* check for synchronization with the original line starts array
3217     	   after pos, if so, the modified range can end early */
3218     	else if (lineStart > pos + nInserted) {
3219     	    adjLineStart = lineStart - nInserted + nDeleted;
3220     	    while (visLineNum<nVisLines && lineStarts[visLineNum]<adjLineStart)
3221     	    	visLineNum++;
3222     	    if (visLineNum < nVisLines && lineStarts[visLineNum] != -1 &&
3223     	    	    lineStarts[visLineNum] == adjLineStart) {
3224     	    	countTo = TextDEndOfLine(textD, lineStart, True);
3225     	    	*modRangeEnd = lineStart;
3226     	    	break;
3227     	    }
3228     	}
3229     }
3230     *linesInserted = nLines;
3231 
3232 
3233     /* Count deleted lines between countFrom and countTo as the text existed
3234        before the modification (that is, as if the text between pos and
3235        pos+nInserted were replaced by "deletedText").  This extra context is
3236        necessary because wrapping can occur outside of the modified region
3237        as a result of adding or deleting text in the region. This is done by
3238        creating a textBuffer containing the deleted text and the necessary
3239        additional context, and calling the wrappedLineCounter on it.
3240 
3241        NOTE: This must not be done in continuous wrap mode when the font
3242 	     width is not fixed. In that case, the calculation would try
3243 	     to access style information that is no longer available (deleted
3244 	     text), or out of date (updated highlighting), possibly leading
3245 	     to completely wrong calculations and/or even crashes eventually.
3246 	     (This is not theoretical; it really happened.)
3247 
3248 	     In that case, the calculation of the number of deleted lines
3249 	     has happened before the buffer was modified (only in that case,
3250 	     because resynchronization of the line starts is impossible
3251 	     in that case, which makes the whole calculation less efficient).
3252     */
3253     if (textD->suppressResync) {
3254 	*linesDeleted = textD->nLinesDeleted;
3255 	textD->suppressResync = 0;
3256 	return;
3257     }
3258 
3259     length = (pos-countFrom) + nDeleted +(countTo-(pos+nInserted));
3260     deletedTextBuf = BufCreatePreallocated(length);
3261     if (pos > countFrom)
3262         BufCopyFromBuf(textD->buffer, deletedTextBuf, countFrom, pos, 0);
3263     if (nDeleted != 0)
3264 	BufInsert(deletedTextBuf, pos-countFrom, deletedText);
3265     if (countTo > pos+nInserted)
3266 	BufCopyFromBuf(textD->buffer, deletedTextBuf,
3267     	    pos+nInserted, countTo, pos-countFrom+nDeleted);
3268     /* Note that we need to take into account an offset for the style buffer:
3269        the deletedTextBuf can be out of sync with the style buffer. */
3270     wrappedLineCounter(textD, deletedTextBuf, 0, length, INT_MAX, True,
3271 	    countFrom, &retPos, &retLines, &retLineStart, &retLineEnd);
3272     BufFree(deletedTextBuf);
3273     *linesDeleted = retLines;
3274     textD->suppressResync = 0;
3275 }
3276 
3277 /*
3278 ** This is a stripped-down version of the findWrapRange() function above,
3279 ** intended to be used to calculate the number of "deleted" lines during
3280 ** a buffer modification. It is called _before_ the modification takes place.
3281 **
3282 ** This function should only be called in continuous wrap mode with a
3283 ** non-fixed font width. In that case, it is impossible to calculate
3284 ** the number of deleted lines, because the necessary style information
3285 ** is no longer available _after_ the modification. In other cases, we
3286 ** can still perform the calculation afterwards (possibly even more
3287 ** efficiently).
3288 */
measureDeletedLines(textDisp * textD,int pos,int nDeleted)3289 static void measureDeletedLines(textDisp *textD, int pos, int nDeleted)
3290 {
3291     int retPos, retLines, retLineStart, retLineEnd;
3292     textBuffer *buf = textD->buffer;
3293     int nVisLines = textD->nVisibleLines;
3294     int *lineStarts = textD->lineStarts;
3295     int countFrom, lineStart;
3296     int nLines = 0, i;
3297     /*
3298     ** Determine where to begin searching: either the previous newline, or
3299     ** if possible, limit to the start of the (original) previous displayed
3300     ** line, using information from the existing line starts array
3301     */
3302     if (pos >= textD->firstChar && pos <= textD->lastChar) {
3303     	for (i=nVisLines-1; i>0; i--)
3304     	    if (lineStarts[i] != -1 && pos >= lineStarts[i])
3305     		break;
3306     	if (i > 0) {
3307     	    countFrom = lineStarts[i-1];
3308     	} else
3309     	    countFrom = BufStartOfLine(buf, pos);
3310     } else
3311     	countFrom = BufStartOfLine(buf, pos);
3312 
3313     /*
3314     ** Move forward through the (new) text one line at a time, counting
3315     ** displayed lines, and looking for either a real newline, or for the
3316     ** line starts to re-sync with the original line starts array
3317     */
3318     lineStart = countFrom;
3319     while (True) {
3320     	/* advance to the next line.  If the line ended in a real newline
3321     	   or the end of the buffer, that's far enough */
3322     	wrappedLineCounter(textD, buf, lineStart, buf->length, 1, True, 0,
3323     	    	&retPos, &retLines, &retLineStart, &retLineEnd);
3324     	if (retPos >= buf->length) {
3325     	    if (retPos != retLineEnd)
3326     	    	nLines++;
3327     	    break;
3328     	} else
3329     	    lineStart = retPos;
3330     	nLines++;
3331     	if (lineStart > pos + nDeleted &&
3332     	    	BufGetCharacter(buf, lineStart-1) == '\n') {
3333     	    break;
3334     	}
3335 
3336 	/* Unlike in the findWrapRange() function above, we don't try to
3337 	   resync with the line starts, because we don't know the length
3338 	   of the inserted text yet, nor the updated style information.
3339 
3340 	   Because of that, we also shouldn't resync with the line starts
3341 	   after the modification either, because we must perform the
3342 	   calculations for the deleted and inserted lines in the same way.
3343 
3344 	   This can result in some unnecessary recalculation and redrawing
3345 	   overhead, and therefore we should only use this two-phase mode
3346 	   of calculation when it's really needed (continuous wrap + variable
3347 	   font width). */
3348     }
3349     textD->nLinesDeleted = nLines;
3350     textD->suppressResync = 1;
3351 }
3352 
3353 /*
3354 ** Count forward from startPos to either maxPos or maxLines (whichever is
3355 ** reached first), and return all relevant positions and line count.
3356 ** The provided textBuffer may differ from the actual text buffer of the
3357 ** widget. In that case it must be a (partial) copy of the actual text buffer
3358 ** and the styleBufOffset argument must indicate the starting position of the
3359 ** copy, to take into account the correct style information.
3360 **
3361 ** Returned values:
3362 **
3363 **   retPos:	    Position where counting ended.  When counting lines, the
3364 **  	    	    position returned is the start of the line "maxLines"
3365 **  	    	    lines beyond "startPos".
3366 **   retLines:	    Number of line breaks counted
3367 **   retLineStart:  Start of the line where counting ended
3368 **   retLineEnd:    End position of the last line traversed
3369 */
wrappedLineCounter(const textDisp * textD,const textBuffer * buf,int startPos,int maxPos,int maxLines,Boolean startPosIsLineStart,int styleBufOffset,int * retPos,int * retLines,int * retLineStart,int * retLineEnd)3370 static void wrappedLineCounter(const textDisp* textD, const textBuffer* buf,
3371         int startPos, int maxPos, int maxLines,
3372         Boolean startPosIsLineStart, int styleBufOffset,
3373         int* retPos, int* retLines, int* retLineStart, int* retLineEnd)
3374 {
3375     int lineStart, newLineStart = 0, b, p, colNum, wrapMargin;
3376     int maxWidth, width, countPixels, i, foundBreak;
3377     int nLines = 0, tabDist = textD->buffer->tabDist;
3378     unsigned char c;
3379     char nullSubsChar = textD->buffer->nullSubsChar;
3380 
3381     /* If the font is fixed, or there's a wrap margin set, it's more efficient
3382        to measure in columns, than to count pixels.  Determine if we can count
3383        in columns (countPixels == False) or must count pixels (countPixels ==
3384        True), and set the wrap target for either pixels or columns */
3385     if (textD->fixedFontWidth != -1 || textD->wrapMargin != 0) {
3386     	countPixels = False;
3387 	wrapMargin = textD->wrapMargin != 0 ? textD->wrapMargin :
3388             	textD->width / textD->fixedFontWidth;
3389         maxWidth = INT_MAX;
3390     } else {
3391     	countPixels = True;
3392     	wrapMargin = INT_MAX;
3393     	maxWidth = textD->width;
3394     }
3395 
3396     /* Find the start of the line if the start pos is not marked as a
3397        line start. */
3398     if (startPosIsLineStart)
3399 	lineStart = startPos;
3400     else
3401 	lineStart = TextDStartOfLine(textD, startPos);
3402 
3403     /*
3404     ** Loop until position exceeds maxPos or line count exceeds maxLines.
3405     ** (actually, contines beyond maxPos to end of line containing maxPos,
3406     ** in case later characters cause a word wrap back before maxPos)
3407     */
3408     colNum = 0;
3409     width = 0;
3410     for (p=lineStart; p<buf->length; p++) {
3411     	c = BufGetCharacter(buf, p);
3412 
3413     	/* If the character was a newline, count the line and start over,
3414     	   otherwise, add it to the width and column counts */
3415     	if (c == '\n') {
3416     	    if (p >= maxPos) {
3417     		*retPos = maxPos;
3418     		*retLines = nLines;
3419     		*retLineStart = lineStart;
3420     		*retLineEnd = maxPos;
3421     		return;
3422     	    }
3423     	    nLines++;
3424     	    if (nLines >= maxLines) {
3425     		*retPos = p + 1;
3426     		*retLines = nLines;
3427     		*retLineStart = p + 1;
3428     		*retLineEnd = p;
3429     		return;
3430     	    }
3431     	    lineStart = p + 1;
3432     	    colNum = 0;
3433     	    width = 0;
3434     	} else {
3435     	    colNum += BufCharWidth(c, colNum, tabDist, nullSubsChar);
3436     	    if (countPixels)
3437     	    	width += measurePropChar(textD, c, colNum, p+styleBufOffset);
3438     	}
3439 
3440     	/* If character exceeded wrap margin, find the break point
3441     	   and wrap there */
3442     	if (colNum > wrapMargin || width > maxWidth) {
3443     	    foundBreak = False;
3444     	    for (b=p; b>=lineStart; b--) {
3445     	    	c = BufGetCharacter(buf, b);
3446     	    	if (c == '\t' || c == ' ') {
3447     	    	    newLineStart = b + 1;
3448     	    	    if (countPixels) {
3449     	    	    	colNum = 0;
3450     	    	    	width = 0;
3451     	    	    	for (i=b+1; i<p+1; i++) {
3452     	    	    	    width += measurePropChar(textD,
3453 				    BufGetCharacter(buf, i), colNum,
3454 				    i+styleBufOffset);
3455     	    	    	    colNum++;
3456     	    	    	}
3457     	    	    } else
3458     	    	    	colNum = BufCountDispChars(buf, b+1, p+1);
3459     	    	    foundBreak = True;
3460     	    	    break;
3461     	    	}
3462     	    }
3463     	    if (!foundBreak) { /* no whitespace, just break at margin */
3464     	    	newLineStart = max(p, lineStart+1);
3465     	    	colNum = BufCharWidth(c, colNum, tabDist, nullSubsChar);
3466     	    	if (countPixels)
3467    	    	    width = measurePropChar(textD, c, colNum, p+styleBufOffset);
3468     	    }
3469     	    if (p >= maxPos) {
3470     		*retPos = maxPos;
3471     		*retLines = maxPos < newLineStart ? nLines : nLines + 1;
3472     		*retLineStart = maxPos < newLineStart ? lineStart :
3473     		    	newLineStart;
3474     		*retLineEnd = maxPos;
3475     		return;
3476     	    }
3477     	    nLines++;
3478     	    if (nLines >= maxLines) {
3479     		*retPos = foundBreak ? b + 1 : max(p, lineStart+1);
3480     		*retLines = nLines;
3481     		*retLineStart = lineStart;
3482     		*retLineEnd = foundBreak ? b : p;
3483     		return;
3484     	    }
3485     	    lineStart = newLineStart;
3486     	}
3487     }
3488 
3489     /* reached end of buffer before reaching pos or line target */
3490     *retPos = buf->length;
3491     *retLines = nLines;
3492     *retLineStart = lineStart;
3493     *retLineEnd = buf->length;
3494 }
3495 
3496 /*
3497 ** Measure the width in pixels of a character "c" at a particular column
3498 ** "colNum" and buffer position "pos".  This is for measuring characters in
3499 ** proportional or mixed-width highlighting fonts.
3500 **
3501 ** A note about proportional and mixed-width fonts: the mixed width and
3502 ** proportional font code in nedit does not get much use in general editing,
3503 ** because nedit doesn't allow per-language-mode fonts, and editing programs
3504 ** in a proportional font is usually a bad idea, so very few users would
3505 ** choose a proportional font as a default.  There are still probably mixed-
3506 ** width syntax highlighting cases where things don't redraw properly for
3507 ** insertion/deletion, though static display and wrapping and resizing
3508 ** should now be solid because they are now used for online help display.
3509 */
measurePropChar(const textDisp * textD,char c,int colNum,int pos)3510 static int measurePropChar(const textDisp* textD, char c,
3511     int colNum, int pos)
3512 {
3513     int charLen, style;
3514     char expChar[MAX_EXP_CHAR_LEN];
3515     textBuffer *styleBuf = textD->styleBuffer;
3516 
3517     charLen = BufExpandCharacter(c, colNum, expChar,
3518 	    textD->buffer->tabDist, textD->buffer->nullSubsChar);
3519     if (styleBuf == NULL) {
3520 	style = 0;
3521     } else {
3522 	style = (unsigned char)BufGetCharacter(styleBuf, pos);
3523 	if (style == textD->unfinishedStyle) {
3524     	    /* encountered "unfinished" style, trigger parsing */
3525     	    (textD->unfinishedHighlightCB)(textD, pos, textD->highlightCBArg);
3526     	    style = (unsigned char)BufGetCharacter(styleBuf, pos);
3527 	}
3528     }
3529     return stringWidth(textD, expChar, charLen, style);
3530 }
3531 
3532 /*
3533 ** Finds both the end of the current line and the start of the next line.  Why?
3534 ** In continuous wrap mode, if you need to know both, figuring out one from the
3535 ** other can be expensive or error prone.  The problem comes when there's a
3536 ** trailing space or tab just before the end of the buffer.  To translate an
3537 ** end of line value to or from the next lines start value, you need to know
3538 ** whether the trailing space or tab is being used as a line break or just a
3539 ** normal character, and to find that out would otherwise require counting all
3540 ** the way back to the beginning of the line.
3541 */
findLineEnd(textDisp * textD,int startPos,int startPosIsLineStart,int * lineEnd,int * nextLineStart)3542 static void findLineEnd(textDisp *textD, int startPos, int startPosIsLineStart,
3543     	int *lineEnd, int *nextLineStart)
3544 {
3545     int retLines, retLineStart;
3546 
3547     /* if we're not wrapping use more efficient BufEndOfLine */
3548     if (!textD->continuousWrap) {
3549     	*lineEnd = BufEndOfLine(textD->buffer, startPos);
3550     	*nextLineStart = min(textD->buffer->length, *lineEnd + 1);
3551     	return;
3552     }
3553 
3554     /* use the wrapped line counter routine to count forward one line */
3555     wrappedLineCounter(textD, textD->buffer, startPos, textD->buffer->length,
3556     	    1, startPosIsLineStart, 0, nextLineStart, &retLines,
3557     	    &retLineStart, lineEnd);
3558     return;
3559 }
3560 
3561 /*
3562 ** Line breaks in continuous wrap mode usually happen at newlines or
3563 ** whitespace.  This line-terminating character is not included in line
3564 ** width measurements and has a special status as a non-visible character.
3565 ** However, lines with no whitespace are wrapped without the benefit of a
3566 ** line terminating character, and this distinction causes endless trouble
3567 ** with all of the text display code which was originally written without
3568 ** continuous wrap mode and always expects to wrap at a newline character.
3569 **
3570 ** Given the position of the end of the line, as returned by TextDEndOfLine
3571 ** or BufEndOfLine, this returns true if there is a line terminating
3572 ** character, and false if there's not.  On the last character in the
3573 ** buffer, this function can't tell for certain whether a trailing space was
3574 ** used as a wrap point, and just guesses that it wasn't.  So if an exact
3575 ** accounting is necessary, don't use this function.
3576 */
wrapUsesCharacter(textDisp * textD,int lineEndPos)3577 static int wrapUsesCharacter(textDisp *textD, int lineEndPos)
3578 {
3579     char c;
3580 
3581     if (!textD->continuousWrap || lineEndPos == textD->buffer->length)
3582     	return True;
3583 
3584     c = BufGetCharacter(textD->buffer, lineEndPos);
3585     return c == '\n' || ((c == '\t' || c == ' ') &&
3586     	    lineEndPos + 1 != textD->buffer->length);
3587 }
3588 
3589 /*
3590 ** Decide whether the user needs (or may need) a horizontal scroll bar,
3591 ** and manage or unmanage the scroll bar widget accordingly.  The H.
3592 ** scroll bar is only hidden in continuous wrap mode when it's absolutely
3593 ** certain that the user will not need it: when wrapping is set
3594 ** to the window edge, or when the wrap margin is strictly less than
3595 ** the longest possible line.
3596 */
hideOrShowHScrollBar(textDisp * textD)3597 static void hideOrShowHScrollBar(textDisp *textD)
3598 {
3599     if (textD->continuousWrap && (textD->wrapMargin == 0 || textD->wrapMargin *
3600     	    textD->fontStruct->max_bounds.width < textD->width))
3601     	XtUnmanageChild(textD->hScrollBar);
3602     else
3603     	XtManageChild(textD->hScrollBar);
3604 }
3605 
3606 /*
3607 ** Return true if the selection "sel" is rectangular, and touches a
3608 ** buffer position withing "rangeStart" to "rangeEnd"
3609 */
rangeTouchesRectSel(selection * sel,int rangeStart,int rangeEnd)3610 static int rangeTouchesRectSel(selection *sel, int rangeStart, int rangeEnd)
3611 {
3612     return sel->selected && sel->rectangular && sel->end >= rangeStart &&
3613     	    sel->start <= rangeEnd;
3614 }
3615 
3616 /*
3617 ** Extend the range of a redraw request (from *start to *end) with additional
3618 ** redraw requests resulting from changes to the attached style buffer (which
3619 ** contains auxiliary information for coloring or styling text).
3620 */
extendRangeForStyleMods(textDisp * textD,int * start,int * end)3621 static void extendRangeForStyleMods(textDisp *textD, int *start, int *end)
3622 {
3623     selection *sel = &textD->styleBuffer->primary;
3624     int extended = False;
3625 
3626     /* The peculiar protocol used here is that modifications to the style
3627        buffer are marked by selecting them with the buffer's primary selection.
3628        The style buffer is usually modified in response to a modify callback on
3629        the text buffer BEFORE textDisp.c's modify callback, so that it can keep
3630        the style buffer in step with the text buffer.  The style-update
3631        callback can't just call for a redraw, because textDisp hasn't processed
3632        the original text changes yet.  Anyhow, to minimize redrawing and to
3633        avoid the complexity of scheduling redraws later, this simple protocol
3634        tells the text display's buffer modify callback to extend it's redraw
3635        range to show the text color/and font changes as well. */
3636     if (sel->selected) {
3637 	if (sel->start < *start) {
3638 	    *start = sel->start;
3639 	    extended = True;
3640 	}
3641 	if (sel->end > *end) {
3642 	    *end = sel->end;
3643 	    extended = True;
3644 	}
3645     }
3646 
3647     /* If the selection was extended due to a style change, and some of the
3648        fonts don't match in spacing, extend redraw area to end of line to
3649        redraw characters exposed by possible font size changes */
3650     if (textD->fixedFontWidth == -1 && extended)
3651     	*end = BufEndOfLine(textD->buffer, *end) + 1;
3652 }
3653 
3654 /**********************  Backlight Functions ******************************/
3655 /*
3656 ** Allocate a read-only (shareable) colormap cell for a named color, from the
3657 ** the default colormap of the screen on which the widget (w) is displayed. If
3658 ** the colormap is full and there's no suitable substitute, print an error on
3659 ** stderr, and return the widget's background color as a backup.
3660 */
allocBGColor(Widget w,char * colorName,int * ok)3661 static Pixel allocBGColor(Widget w, char *colorName, int *ok)
3662 {
3663     int r,g,b;
3664     *ok = 1;
3665     return AllocColor(w, colorName, &r, &g, &b);
3666 }
3667 
getRangesetColor(textDisp * textD,int ind,Pixel bground)3668 static Pixel getRangesetColor(textDisp *textD, int ind, Pixel bground)
3669 {
3670     textBuffer *buf;
3671     RangesetTable *tab;
3672     Pixel color;
3673     char *color_name;
3674     int valid;
3675 
3676     if (ind > 0) {
3677       ind--;
3678       buf = textD->buffer;
3679       tab = buf->rangesetTable;
3680 
3681       valid = RangesetTableGetColorValid(tab, ind, &color);
3682       if (valid == 0) {
3683           color_name = RangesetTableGetColorName(tab, ind);
3684           if (color_name)
3685               color = allocBGColor(textD->w, color_name, &valid);
3686           RangesetTableAssignColorPixel(tab, ind, color, valid);
3687       }
3688       if (valid > 0) {
3689           return color;
3690       }
3691     }
3692     return bground;
3693 }
3694 
3695 /*
3696 ** Read the background color class specification string in str, allocating the
3697 ** necessary colors, and allocating and setting up the character->class_no and
3698 ** class_no->pixel map arrays, returned via *pp_bgClass and *pp_bgClassPixel
3699 ** respectively.
3700 ** Note: the allocation of class numbers could be more intelligent: there can
3701 ** never be more than 256 of these (one per character); but I don't think
3702 ** there'll be a pressing need. I suppose the scanning of the specification
3703 ** could be better too, but then, who cares!
3704 */
TextDSetupBGClasses(Widget w,XmString str,Pixel ** pp_bgClassPixel,unsigned char ** pp_bgClass,Pixel bgPixelDefault)3705 void TextDSetupBGClasses(Widget w, XmString str, Pixel **pp_bgClassPixel,
3706       unsigned char **pp_bgClass, Pixel bgPixelDefault)
3707 {
3708     unsigned char bgClass[256];
3709     Pixel bgClassPixel[256];
3710     int class_no = 0;
3711     char *semicol;
3712     char *s = (char *)str;
3713     size_t was_semicol;
3714     int lo, hi, dummy;
3715     char *pos;
3716     Boolean is_good = True;
3717 
3718     NEditFree(*pp_bgClass);
3719     NEditFree(*pp_bgClassPixel);
3720 
3721     *pp_bgClassPixel = NULL;
3722     *pp_bgClass = NULL;
3723 
3724     if (!s)
3725       return;
3726 
3727     /* default for all chars is class number zero, for standard background */
3728     memset(bgClassPixel, 0, sizeof bgClassPixel);
3729     memset(bgClass, 0, sizeof bgClass);
3730     bgClassPixel[0] = bgPixelDefault;
3731     /* since class no == 0 in a "style" has no set bits in BACKLIGHT_MASK
3732        (see styleOfPos()), when drawString() is called for text with a
3733        backlight class no of zero, bgClassPixel[0] is never consulted, and
3734        the default background color is chosen. */
3735 
3736     /* The format of the class string s is:
3737               low[-high]{,low[-high]}:color{;low-high{,low[-high]}:color}
3738           eg
3739               32-255:#f0f0f0;1-31,127:red;128-159:orange;9-13:#e5e5e5
3740        where low and high represent a character range between ordinal
3741        ASCII values. Using strtol() allows automatic octal, dec and hex
3742        reading of low and high. The example format sets backgrounds as follows:
3743               char   1 - 8    colored red     (control characters)
3744               char   9 - 13   colored #e5e5e5 (isspace() control characters)
3745               char  14 - 31   colored red     (control characters)
3746               char  32 - 126  colored #f0f0f0
3747               char 127        colored red     (delete character)
3748               char 128 - 159  colored orange  ("shifted" control characters)
3749               char 160 - 255  colored #f0f0f0
3750        Notice that some of the later ranges overwrite the class values defined
3751        for earlier ones (eg the first clause, 32-255:#f0f0f0 sets the DEL
3752        character background color to #f0f0f0; it is then set to red by the
3753        clause 1-31,127:red). */
3754 
3755     while (s && class_no < 255) {
3756         class_no++;                   /* simple class alloc scheme */
3757       was_semicol = 0;
3758       is_good = True;
3759       if ((semicol = (char *)strchr(s, ';'))) {
3760           *semicol = '\0';    /* null-terminate low[-high]:color clause */
3761           was_semicol = 1;
3762       }
3763 
3764       /* loop over ranges before the color spec, assigning the characters
3765          in the ranges to the current class number */
3766       for (lo = hi = strtol(s, &pos, 0);
3767            is_good;
3768            lo = hi = strtol(pos + 1, &pos, 0)) {
3769           if (pos && *pos == '-')
3770               hi = strtol(pos + 1, &pos, 0);  /* get end of range */
3771           is_good = (pos && 0 <= lo && lo <= hi && hi <= 255);
3772           if (is_good)
3773               while (lo <= hi)
3774                   bgClass[lo++] = (unsigned char)class_no;
3775           if (*pos != ',')
3776               break;
3777       }
3778       if ((is_good = (is_good && *pos == ':'))) {
3779           is_good = (*pos++ != '\0');         /* pos now points to color */
3780           bgClassPixel[class_no] = allocBGColor(w, pos, &dummy);
3781       }
3782       if (!is_good) {
3783           /* complain? this class spec clause (in string s) was faulty */
3784       }
3785 
3786       /* end of loop iterator clauses */
3787       if (was_semicol)
3788         *semicol = ';';       /* un-null-terminate low[-high]:color clause */
3789       s = semicol + was_semicol;
3790     }
3791 
3792     /* when we get here, we've set up our class table and class-to-pixel table
3793        in local variables: now put them into the "real thing" */
3794     class_no++;                     /* bigger than all valid class_nos */
3795     *pp_bgClass = (unsigned char *)NEditMalloc(256);
3796     *pp_bgClassPixel = (Pixel *)NEditMalloc(class_no * sizeof (Pixel));
3797     if (!*pp_bgClass || !*pp_bgClassPixel) {
3798         NEditFree(*pp_bgClass);
3799         NEditFree(*pp_bgClassPixel);
3800         return;
3801     }
3802     memcpy(*pp_bgClass, bgClass, 256);
3803     memcpy(*pp_bgClassPixel, bgClassPixel, class_no * sizeof (Pixel));
3804 }
3805