UNIXworkcode

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 #include <ctype.h> 48 #ifndef __MVS__ 49 #include <sys/param.h> 50 #endif 51 52 #include <Xm/Xm.h> 53 #include <Xm/ScrolledW.h> 54 #include <Xm/ScrollBar.h> 55 #include <Xm/Label.h> 56 #include <X11/Shell.h> 57 58 #ifdef HAVE_DEBUG_H 59 #include "../debug.h" 60 #endif 61 62 /* Masks for text drawing methods. These are or'd together to form an 63 integer which describes what drawing calls to use to draw a string */ 64 #define FILL_SHIFT 8 65 #define SECONDARY_SHIFT 9 66 #define PRIMARY_SHIFT 10 67 #define HIGHLIGHT_SHIFT 11 68 #define STYLE_LOOKUP_SHIFT 0 69 #define BACKLIGHT_SHIFT 12 70 71 #define FILL_MASK (1 << FILL_SHIFT) 72 #define SECONDARY_MASK (1 << SECONDARY_SHIFT) 73 #define PRIMARY_MASK (1 << PRIMARY_SHIFT) 74 #define HIGHLIGHT_MASK (1 << HIGHLIGHT_SHIFT) 75 #define STYLE_LOOKUP_MASK (0xff << STYLE_LOOKUP_SHIFT) 76 #define BACKLIGHT_MASK (0xff << BACKLIGHT_SHIFT) 77 78 #define RANGESET_SHIFT (20) 79 #define RANGESET_MASK (0x3F << RANGESET_SHIFT) 80 81 /* If you use both 32-Bit Style mask layout: 82 Bits +----------------+----------------+----------------+----------------+ 83 hex |1F1E1D1C1B1A1918|1716151413121110| F E D C B A 9 8| 7 6 5 4 3 2 1 0| 84 dec |3130292827262524|2322212019181716|151413121110 9 8| 7 6 5 4 3 2 1 0| 85 +----------------+----------------+----------------+----------------+ 86 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| 87 +----------------+----------------+----------------+----------------+ 88 where: s - style lookup value (8 bits) 89 F - fill (1 bit) 90 2 - secondary selection (1 bit) 91 1 - primary selection (1 bit) 92 H - highlight (1 bit) 93 b - backlighting index (8 bits) 94 r - rangeset index (6 bits) 95 This leaves 6 "unused" bits */ 96 97 /* Maximum displayable line length (how many characters will fit across the 98 widest window). This amount of memory is temporarily allocated from the 99 stack in the redisplayLine routine for drawing strings */ 100 #define MAX_DISP_LINE_LEN 1000 101 102 /* Macro for getting the TextPart from a textD */ 103 #define TEXT_OF_TEXTD(t) (((TextWidget)((t)->w))->text) 104 105 #define MCURSOR_ALLOC 8 106 #define MCURSOR_MAX 1024 107 #define MCURSOR_ALLOC_RESET 32 108 109 enum positionTypes {CURSOR_POS, CHARACTER_POS}; 110 111 static void updateLineStarts(textDisp *textD, int pos, int charsInserted, 112 int charsDeleted, int linesInserted, int linesDeleted, int *scrolled); 113 static void offsetLineStarts(textDisp *textD, int newTopLineNum); 114 static void calcLineStarts(textDisp *textD, int startLine, int endLine); 115 static void calcLastChar(textDisp *textD); 116 static int posToVisibleLineNum(textDisp *textD, int pos, int *lineNum); 117 static int getCharWidth(textDisp *textD, const char *src_orig, FcChar32 *dst, int len); 118 static FcChar32 getCharacter32(const textDisp *textD, const textBuffer* buf, int pos, int *charlen); 119 static void redisplayLine(textDisp *textD, int visLineNum, int leftClip, 120 int rightClip, int leftCharIndex, int rightCharIndex); 121 static void drawString(textDisp *textD, int style, int rbIndex, int x, int y, int fromX, 122 int toX, FcChar32 *string, int nChars, Boolean highlightLine, ansiStyle *ansi); 123 static void clearRect(textDisp *textD, XftColor *color, int x, int y, 124 int width, int height); 125 static void drawCursor(textDisp *textD, int x, int y); 126 static int styleOfPos(textDisp *textD, int lineStartPos, 127 int lineLen, int lineIndex, int dispIndex, int thisChar); 128 static int charWidth4(const textDisp* textD, const FcChar32* string, 129 int length, NFont *font); 130 static NFont* styleFontList(const textDisp* textD, int style); 131 static int inSelection(selection *sel, int pos, int lineStartPos, 132 int dispIndex); 133 static int xyToPos(textDisp *textD, int x, int y, int posType); 134 static void xyToUnconstrainedPos(textDisp *textD, int x, int y, int *row, 135 int *column, int posType); 136 static void bufPreDeleteCB(int pos, int nDeleted, void *cbArg); 137 static void bufModifiedCB(int pos, int nInserted, int nDeleted, 138 int nRestyled, const char *deletedText, void *cbArg); 139 static void setScroll(textDisp *textD, int topLineNum, int horizOffset, 140 int updateVScrollBar, int updateHScrollBar); 141 static void hScrollCB(Widget w, XtPointer clientData, XtPointer callData); 142 static void vScrollCB(Widget w, XtPointer clientData, XtPointer callData); 143 static void visibilityEH(Widget w, XtPointer data, XEvent *event, 144 Boolean *continueDispatch); 145 static void redrawLineNumbers(textDisp *textD, int top, int height, int clearAll); 146 static void updateVScrollBarRange(textDisp *textD); 147 static int updateHScrollBarRange(textDisp *textD); 148 static int max(int i1, int i2); 149 static int min(int i1, int i2); 150 static int countLines(const char *string); 151 static int measureVisLine(textDisp *textD, int visLineNum); 152 static int emptyLinesVisible(textDisp *textD); 153 static void blankSingleCursorProtrusions(textDisp *textD); 154 static void blankCursorProtrusions(textDisp *textD); 155 static void allocateFixedFontGCs(textDisp *textD, Pixel bgPixel, Pixel fgPixel); 156 static GC allocateGC(Widget w, unsigned long valueMask, 157 unsigned long foreground, unsigned long background, Font font, 158 unsigned long dynamicMask, unsigned long dontCareMask); 159 static void releaseGC(Widget w, GC gc); 160 static void resetClipRectangles(textDisp *textD); 161 static int visLineLength(textDisp *textD, int visLineNum); 162 static void measureDeletedLines(textDisp *textD, int pos, int nDeleted); 163 static int findWrapRange(textDisp *textD, const char *deletedText, int pos, 164 int nInserted, int nDeleted, int *modRangeStart, int *modRangeEnd, 165 int *linesInserted, int *linesDeleted); 166 static void wrappedLineCounter(const textDisp* textD, const textBuffer* buf, 167 int startPos, int maxPos, int maxLines, 168 Boolean startPosIsLineStart, int styleBufOffset, 169 int* retPos, int* retLines, int* retLineStart, int* retLineEnd, 170 Boolean *retWrap); 171 static void findLineEnd(textDisp *textD, int startPos, int startPosIsLineStart, 172 int *lineEnd, int *nextLineStart); 173 static int wrapUsesCharacter(textDisp *textD, int lineEndPos); 174 static void hideOrShowHScrollBar(textDisp *textD); 175 static int rangeTouchesRectSel(selection *sel, int rangeStart, int rangeEnd); 176 static void extendRangeForStyleMods(textDisp *textD, int *start, int *end); 177 static int getAbsTopLineNum(textDisp *textD); 178 static void offsetAbsLineNum(textDisp *textD, int oldFirstChar); 179 static int maintainingAbsTopLineNum(textDisp *textD); 180 static void resetAbsLineNum(textDisp *textD); 181 static int measurePropChar(const textDisp* textD, FcChar32 c, 182 int colNum, int pos); 183 static XftColor allocBGColor(Widget w, char *colorName, int *ok); 184 static XftColor* getRangesetColor(textDisp *textD, int ind, XftColor *bground); 185 static void textDRedisplayRange(textDisp *textD, int start, int end); 186 static void findActiveAnsiStyle(textDisp *textD, ssize_t pos, ansiStyle *style); 187 static int parseEscapeSequence(textBuffer *buf, size_t pos, ansiStyle *style); 188 static void extendAnsiStyle(ansiStyle *style, ansiStyle *ext); 189 static void ansiFgToColorIndex(textDisp *textD, short fg, XftColor *color); 190 static void ansiBgToColorIndex(textDisp *textD, short bg, XftColor *color); 191 192 textDisp *TextDCreate(Widget widget, Widget hScrollBar, Widget vScrollBar, 193 Position left, Position top, Position width, Position height, 194 Position lineNumLeft, Position lineNumWidth, Position marginWidth, 195 textBuffer *buffer, NFont *font, NFont *bold, NFont *italic, 196 NFont *boldItalic, ColorProfile *colorProfile, int continuousWrap, 197 int wrapMargin, XmString bgClassString, Pixel calltipFGPixel, 198 Pixel calltipBGPixel, Pixel lineHighlightBGPixel, Boolean indentRainbow, 199 Boolean highlightCursorLine, Boolean ansiColors) 200 { 201 textDisp *textD; 202 XGCValues gcValues; 203 int i; 204 205 XftFont *xftFont = FontDefault(font); 206 207 textD = (textDisp *)NEditMalloc(sizeof(textDisp)); 208 textD->w = widget; 209 textD->d = NULL; 210 textD->top = top; 211 textD->left = left; 212 textD->width = width; 213 textD->height = height; 214 textD->marginWidth = marginWidth; 215 textD->cursorOn = True; 216 /* 217 textD->cursor->cursorPos = 0; 218 textD->cursor->cursorPosCache = -1; 219 textD->cursor->cursorPosCacheLeft = 0; 220 textD->cursor->cursorPosCacheRight = 0; 221 textD->cursor->x = -100; 222 textD->cursor->y = -100; 223 */ 224 textD->cursorToHint = NO_HINT; 225 textD->cursorStyle = NORMAL_CURSOR; 226 textD->xic_x = 0; 227 textD->xic_y = 0; 228 textD->buffer = buffer; 229 textD->firstChar = 0; 230 textD->lastChar = 0; 231 textD->nBufferLines = 0; 232 textD->topLineNum = 1; 233 textD->absTopLineNum = 1; 234 textD->needAbsTopLineNum = False; 235 textD->horizOffset = 0; 236 textD->visibility = VisibilityUnobscured; 237 textD->hScrollBar = hScrollBar; 238 textD->vScrollBar = vScrollBar; 239 textD->font = FontRef(font); 240 textD->boldFont = FontRef(bold); 241 textD->italicFont = FontRef(italic); 242 textD->boldItalicFont = FontRef(boldItalic); 243 textD->ascent = xftFont->ascent; 244 textD->descent = xftFont->descent; 245 textD->fixedFontWidth = -1; 246 textD->styleBuffer = NULL; 247 textD->styleTable = NULL; 248 textD->nStyles = 0; 249 textD->colorProfile = colorProfile; 250 textD->wrapMargin = wrapMargin; 251 textD->continuousWrap = continuousWrap; 252 allocateFixedFontGCs( 253 textD, colorProfile->textBgColor.pixel, colorProfile->textFgColor.pixel); 254 textD->lineNumLeft = lineNumLeft; 255 textD->lineNumWidth = lineNumWidth; 256 textD->nVisibleLines = (height - 1) / (textD->ascent + textD->descent) + 1; 257 gcValues.foreground = colorProfile->cursorFgColor.pixel; 258 textD->cursorFGGC = XtGetGC(widget, GCForeground, &gcValues); 259 textD->lineStarts = (int *)NEditMalloc(sizeof(int) * textD->nVisibleLines); 260 textD->lineStarts[0] = 0; 261 textD->calltipW = NULL; 262 textD->calltipShell = NULL; 263 textD->calltip.ID = 0; 264 textD->calltipFGPixel = calltipFGPixel; 265 textD->calltipBGPixel = calltipBGPixel; 266 for (i=1; i<textD->nVisibleLines; i++) 267 textD->lineStarts[i] = -1; 268 textD->bgClassPixel = NULL; 269 textD->bgClass = NULL; 270 TextDSetupBGClasses(widget, bgClassString, &textD->bgClassPixel, 271 &textD->bgClass, colorProfile->textBgColor); 272 textD->suppressResync = 0; 273 textD->nLinesDeleted = 0; 274 textD->modifyingTabDist = 0; 275 textD->pointerHidden = False; 276 textD->disableRedisplay = False; 277 textD->fixLeftClipAfterResize = False; 278 textD->graphicsExposeQueue = NULL; 279 textD->indentRainbow = indentRainbow; 280 textD->highlightCursorLine = highlightCursorLine; 281 textD->redrawCursorLine = False; 282 283 284 // Initialize multi cursor array 285 textD->mcursorAlloc = MCURSOR_ALLOC; 286 textD->mcursorSize = 1; 287 textD->mcursorSizeReal = 1; 288 textD->multicursor = NEditCalloc(MCURSOR_ALLOC, sizeof(textCursor)); 289 textD->mcursorOn = FALSE; 290 291 // Initialize main cursor 292 textD->multicursor[0].cursorPos = 0; 293 textD->multicursor[0].cursorPosCache = -1; 294 textD->multicursor[0].cursorPosCacheLeft = 0; 295 textD->multicursor[0].cursorPosCacheRight = 0; 296 textD->multicursor[0].cursorPreferredCol = -1; 297 textD->multicursor[0].x = -100; 298 textD->multicursor[0].y = -100; 299 300 textD->cursor = textD->multicursor; 301 textD->newcursor = textD->multicursor; 302 303 textD->cacheNoWrappingWidth = 0; 304 textD->cacheNoWrapping = False; 305 306 TextDSetAnsiColors(textD, ansiColors); 307 308 /* Attach an event handler to the widget so we can know the visibility 309 (used for choosing the fastest drawing method) */ 310 XtAddEventHandler(widget, VisibilityChangeMask, False, 311 visibilityEH, textD); 312 313 /* Attach the callback to the text buffer for receiving modification 314 information */ 315 if (buffer != NULL) { 316 BufAddModifyCB(buffer, bufModifiedCB, textD); 317 BufAddPreDeleteCB(buffer, bufPreDeleteCB, textD); 318 } 319 320 /* Initialize the scroll bars and attach movement callbacks */ 321 if (vScrollBar != NULL) { 322 XtVaSetValues(vScrollBar, XmNminimum, 1, XmNmaximum, 2, 323 XmNsliderSize, 1, XmNrepeatDelay, 10, XmNvalue, 1, NULL); 324 XtAddCallback(vScrollBar, XmNdragCallback, vScrollCB, (XtPointer)textD); 325 XtAddCallback(vScrollBar, XmNvalueChangedCallback, vScrollCB, 326 (XtPointer)textD); 327 } 328 if (hScrollBar != NULL) { 329 XtVaSetValues(hScrollBar, XmNminimum, 0, XmNmaximum, 1, 330 XmNsliderSize, 1, XmNrepeatDelay, 10, XmNvalue, 0, 331 XmNincrement, font->maxWidth, NULL); 332 XtAddCallback(hScrollBar, XmNdragCallback, hScrollCB, (XtPointer)textD); 333 XtAddCallback(hScrollBar, XmNvalueChangedCallback, hScrollCB, 334 (XtPointer)textD); 335 } 336 337 /* Update the display to reflect the contents of the buffer */ 338 if (buffer != NULL) 339 bufModifiedCB(0, buffer->length, 0, 0, NULL, textD); 340 341 /* Decide if the horizontal scroll bar needs to be visible */ 342 hideOrShowHScrollBar(textD); 343 344 return textD; 345 } 346 347 /* 348 * Initialize the XftDraw object. This should be called after the widget 349 * is realized. 350 */ 351 void TextDInitXft(textDisp *textD) { 352 XWindowAttributes attributes; 353 XGetWindowAttributes(XtDisplay(textD->w), XtWindow(textD->w), &attributes); 354 355 Screen *screen = textD->w->core.screen; 356 Visual *visual = screen->root_visual; 357 for(int i=0;i<screen->ndepths;i++) { 358 Depth d = screen->depths[i]; 359 if(d.depth == textD->w->core.depth) { 360 visual = d.visuals; 361 break; 362 } 363 } 364 365 Display *dp = XtDisplay(textD->w); 366 textD->d = XftDrawCreate( 367 dp, 368 XtWindow(textD->w), 369 visual, 370 textD->w->core.colormap); 371 372 } 373 374 /* 375 ** Free a text display and release its associated memory. Note, the text 376 ** BUFFER that the text display displays is a separate entity and is not 377 ** freed, nor are the style buffer or style table. 378 */ 379 void TextDFree(textDisp *textD) 380 { 381 FontUnref(textD->font); 382 FontUnref(textD->boldFont); 383 FontUnref(textD->italicFont); 384 FontUnref(textD->boldItalicFont); 385 386 NEditFree(textD->multicursor); 387 388 BufRemoveModifyCB(textD->buffer, bufModifiedCB, textD); 389 BufRemovePreDeleteCB(textD->buffer, bufPreDeleteCB, textD); 390 releaseGC(textD->w, textD->gc); 391 NEditFree(textD->lineStarts); 392 while (TextDPopGraphicExposeQueueEntry(textD)) { 393 } 394 NEditFree(textD->bgClassPixel); 395 NEditFree(textD->bgClass); 396 NEditFree(textD); 397 } 398 399 /* 400 ** Attach a text buffer to display, replacing the current buffer (if any) 401 */ 402 void TextDSetBuffer(textDisp *textD, textBuffer *buffer) 403 { 404 /* If the text display is already displaying a buffer, clear it off 405 of the display and remove our callback from it */ 406 if (textD->buffer != NULL) { 407 bufModifiedCB(0, 0, textD->buffer->length, 0, NULL, textD); 408 BufRemoveModifyCB(textD->buffer, bufModifiedCB, textD); 409 BufRemovePreDeleteCB(textD->buffer, bufPreDeleteCB, textD); 410 } 411 412 /* Add the buffer to the display, and attach a callback to the buffer for 413 receiving modification information when the buffer contents change */ 414 textD->buffer = buffer; 415 BufAddModifyCB(buffer, bufModifiedCB, textD); 416 BufAddPreDeleteCB(buffer, bufPreDeleteCB, textD); 417 418 /* Update the display */ 419 bufModifiedCB(0, buffer->length, 0, 0, NULL, textD); 420 } 421 422 /* 423 ** Attach (or remove) highlight information in text display and redisplay. 424 ** Highlighting information consists of a style buffer which parallels the 425 ** normal text buffer, but codes font and color information for the display; 426 ** a style table which translates style buffer codes (indexed by buffer 427 ** character - 65 (ASCII code for 'A')) into fonts and colors; and a callback 428 ** mechanism for as-needed highlighting, triggered by a style buffer entry of 429 ** "unfinishedStyle". Style buffer can trigger additional redisplay during 430 ** a normal buffer modification if the buffer contains a primary selection 431 ** (see extendRangeForStyleMods for more information on this protocol). 432 ** 433 ** Style buffers, tables and their associated memory are managed by the caller. 434 */ 435 void TextDAttachHighlightData(textDisp *textD, textBuffer *styleBuffer, 436 styleTableEntry *styleTable, int nStyles, char unfinishedStyle, 437 unfinishedStyleCBProc unfinishedHighlightCB, void *cbArg) 438 { 439 textD->styleBuffer = styleBuffer; 440 textD->styleTable = styleTable; 441 textD->nStyles = nStyles; 442 textD->unfinishedStyle = unfinishedStyle; 443 textD->unfinishedHighlightCB = unfinishedHighlightCB; 444 textD->highlightCBArg = cbArg; 445 446 /* Call TextDSetFont to combine font information from style table and 447 primary font, adjust font-related parameters, and then redisplay */ 448 TextDSetFont(textD, textD->font); 449 } 450 451 452 void TextDSetColorProfile(textDisp *textD, ColorProfile *profile) 453 { 454 XGCValues values; 455 Display *d = XtDisplay(textD->w); 456 457 textD->colorProfile = profile; 458 459 releaseGC(textD->w, textD->gc); 460 allocateFixedFontGCs(textD, profile->textBgColor.pixel, profile->textFgColor.pixel); 461 462 /* Change the cursor GC (the cursor GC is not shared). */ 463 values.foreground = profile->cursorFgColor.pixel; 464 XChangeGC( d, textD->cursorFGGC, GCForeground, &values ); 465 466 /* Redisplay */ 467 TextDRedisplayRect(textD, textD->left, textD->top, textD->width, 468 textD->height); 469 redrawLineNumbers(textD, textD->top, textD->height, True); 470 } 471 472 /* 473 ** Change the (non highlight) font 474 */ 475 void TextDSetFont(textDisp *textD, NFont *font) 476 { 477 XftFont *fontStruct = FontDefault(font); 478 int i, maxAscent = fontStruct->ascent, maxDescent = fontStruct->descent; 479 int width, height; 480 XftFont *styleFont; 481 NFont *styleFontList; 482 483 /* If font size changes, cursor will be redrawn in a new position */ 484 if(!textD->disableRedisplay) { 485 blankCursorProtrusions(textD); 486 } 487 488 /* If there is a (syntax highlighting) style table in use, find the new 489 maximum font height for this text display */ 490 for (i=0; i<textD->nStyles; i++) { 491 styleFontList = textD->styleTable[i].font; 492 styleFont = styleFontList ? FontDefault(styleFontList) : NULL; 493 if (styleFont != NULL && styleFont->ascent > maxAscent) 494 maxAscent = styleFont->ascent; 495 if (styleFont != NULL && styleFont->descent > maxDescent) 496 maxDescent = styleFont->descent; 497 } 498 textD->ascent = maxAscent; 499 textD->descent = maxDescent; 500 501 // force -1 fixedFontWidth, because the fixed font optimization 502 // doesn't work with unicode 503 textD->fixedFontWidth = -1; 504 505 /* Don't let the height dip below one line, or bad things can happen */ 506 if (textD->height < maxAscent + maxDescent) 507 textD->height = maxAscent + maxDescent; 508 509 if(textD->font != font) { 510 FontUnref(textD->font); 511 textD->font = FontRef(font); 512 } 513 514 if(textD->disableRedisplay) { 515 return; 516 } 517 518 /* Do a full resize to force recalculation of font related parameters */ 519 width = textD->width; 520 height = textD->height; 521 textD->width = textD->height = 0; 522 TextDResize(textD, width, height); 523 524 /* if the shell window doesn't get resized, and the new fonts are 525 of smaller sizes, sometime we get some residual text on the 526 blank space at the bottom part of text area. Clear it here. */ 527 clearRect(textD, &textD->colorProfile->textBgColor, textD->left, 528 textD->top + textD->height - maxAscent - maxDescent, 529 textD->width, maxAscent + maxDescent); 530 531 /* Redisplay */ 532 TextDRedisplayRect(textD, textD->left, textD->top, textD->width, 533 textD->height); 534 535 /* Clean up line number area in case spacing has changed */ 536 redrawLineNumbers(textD, textD->top, textD->height, True); 537 } 538 539 void TextDSetBoldFont(textDisp *textD, NFont *boldFont) 540 { 541 if(textD->boldFont != boldFont) { 542 FontUnref(textD->boldFont); 543 textD->boldFont = FontRef(boldFont); 544 } 545 } 546 547 void TextDSetItalicFont(textDisp *textD, NFont *italicFont) 548 { 549 if(textD->italicFont != italicFont) { 550 FontUnref(textD->italicFont); 551 textD->italicFont = FontRef(italicFont); 552 } 553 } 554 555 void TextDSetBoldItalicFont(textDisp *textD, NFont *boldItalicFont) 556 { 557 if(textD->boldItalicFont != boldItalicFont) { 558 FontUnref(textD->boldItalicFont); 559 textD->boldItalicFont = FontRef(boldItalicFont); 560 } 561 } 562 563 int TextDMinFontWidth(textDisp *textD, Boolean considerStyles) 564 { 565 int fontWidth = textD->font->minWidth; 566 int i; 567 568 if (considerStyles) { 569 for (i = 0; i < textD->nStyles; ++i) { 570 int thisWidth = (textD->styleTable[i].font)->minWidth; 571 if (thisWidth < fontWidth) { 572 fontWidth = thisWidth; 573 } 574 } 575 } 576 return fontWidth; 577 } 578 579 int TextDMaxFontWidth(textDisp *textD, Boolean considerStyles) 580 { 581 int fontWidth = textD->font->maxWidth; 582 int i; 583 584 if (considerStyles) { 585 for (i = 0; i < textD->nStyles; ++i) { 586 int thisWidth = textD->styleTable[i].font->maxWidth; 587 if (thisWidth > fontWidth) { 588 fontWidth = thisWidth; 589 } 590 } 591 } 592 return fontWidth; 593 } 594 595 /* 596 ** Change the size of the displayed text area 597 */ 598 void TextDResize(textDisp *textD, int width, int height) 599 { 600 int oldVisibleLines = textD->nVisibleLines; 601 int canRedraw = XtWindow(textD->w) != 0; 602 int newVisibleLines = height / (textD->ascent + textD->descent); 603 int redrawAll = False; 604 int oldWidth = textD->width; 605 int exactHeight = height - height % (textD->ascent + textD->descent); 606 607 if(width > oldWidth) { 608 textD->fixLeftClipAfterResize = True; 609 } 610 611 textD->width = width; 612 textD->height = height; 613 614 if(width < textD->cacheNoWrappingWidth) { 615 textD->cacheNoWrapping = False; 616 } 617 618 /* In continuous wrap mode, a change in width affects the total number of 619 lines in the buffer, and can leave the top line number incorrect, and 620 the top character no longer pointing at a valid line start */ 621 if (textD->continuousWrap && textD->wrapMargin==0 && width!=oldWidth && !textD->cacheNoWrapping) { 622 Boolean wrap0 = False; 623 Boolean wrap1 = False; 624 625 int oldFirstChar = textD->firstChar; 626 627 textD->firstChar = TextDStartOfLine(textD, textD->firstChar); 628 int toplinenum = TextDCountLinesW(textD, 0, textD->firstChar, True, &wrap0); 629 int count = TextDCountLinesW(textD, textD->firstChar, textD->buffer->length, True, &wrap1); 630 631 632 textD->nBufferLines = toplinenum + count; 633 634 if(!(wrap0 || wrap1)) { 635 textD->cacheNoWrapping = True; 636 textD->cacheNoWrappingWidth = width; 637 } 638 639 textD->topLineNum = toplinenum+1; 640 redrawAll = True; 641 offsetAbsLineNum(textD, oldFirstChar); 642 } 643 644 /* reallocate and update the line starts array, which may have changed 645 size and/or contents. (contents can change in continuous wrap mode 646 when the width changes, even without a change in height) */ 647 if (oldVisibleLines < newVisibleLines) { 648 NEditFree(textD->lineStarts); 649 textD->lineStarts = (int *)NEditMalloc(sizeof(int) * newVisibleLines); 650 } 651 textD->nVisibleLines = newVisibleLines; 652 calcLineStarts(textD, 0, newVisibleLines); 653 calcLastChar(textD); 654 655 /* if the window became shorter, there may be partially drawn 656 text left at the bottom edge, which must be cleaned up */ 657 if (canRedraw && oldVisibleLines>newVisibleLines && exactHeight!=height) 658 XClearArea(XtDisplay(textD->w), XtWindow(textD->w), textD->left, 659 textD->top + exactHeight, textD->width, 660 height - exactHeight, False); 661 662 /* if the window became taller, there may be an opportunity to display 663 more text by scrolling down */ 664 if (canRedraw && oldVisibleLines < newVisibleLines && textD->topLineNum + 665 textD->nVisibleLines > textD->nBufferLines) 666 setScroll(textD, max(1, textD->nBufferLines - textD->nVisibleLines + 667 2 + TEXT_OF_TEXTD(textD).cursorVPadding), 668 textD->horizOffset, False, False); 669 670 /* Update the scroll bar page increment size (as well as other scroll 671 bar parameters. If updating the horizontal range caused scrolling, 672 redraw */ 673 updateVScrollBarRange(textD); 674 if (updateHScrollBarRange(textD)) 675 redrawAll = True; 676 677 /* If a full redraw is needed */ 678 if (redrawAll && canRedraw) 679 TextDRedisplayRect(textD, textD->left, textD->top, textD->width, 680 textD->height); 681 682 /* Decide if the horizontal scroll bar needs to be visible */ 683 hideOrShowHScrollBar(textD); 684 685 /* Refresh the line number display to draw more line numbers, or 686 erase extras */ 687 redrawLineNumbers(textD, textD->top, textD->height, True); 688 689 /* Redraw the calltip */ 690 TextDRedrawCalltip(textD, 0); 691 } 692 693 /* 694 ** Refresh a rectangle of the text display. left and top are in coordinates of 695 ** the text drawing window 696 */ 697 void TextDRedisplayRect(textDisp *textD, int left, int top, int width, 698 int height) 699 { 700 int fontHeight, firstLine, lastLine, line; 701 if(textD->fixLeftClipAfterResize) { 702 // this call was directly after a window resize 703 // the left clip could be in the middle of a glyph, that was previously 704 // not rendered 705 // as a result, only the right half of the glyph would be rendered now 706 // to fix this, decrease the left a bit 707 int changeLeftClip = textD->font->fonts->font->max_advance_width; 708 if(left > changeLeftClip) { 709 left -= changeLeftClip; 710 width += changeLeftClip; 711 } 712 textD->fixLeftClipAfterResize = False; 713 } 714 715 /* find the line number range of the display */ 716 fontHeight = textD->ascent + textD->descent; 717 firstLine = (top - textD->top - fontHeight + 1) / fontHeight; 718 lastLine = (top + height - textD->top) / fontHeight; 719 720 /* If the graphics contexts are shared using XtAllocateGC, their 721 clipping rectangles may have changed since the last use */ 722 resetClipRectangles(textD); 723 724 /* draw the lines of text */ 725 for (line=firstLine; line<=lastLine; line++) 726 redisplayLine(textD, line, left-textD->marginWidth, left+width, 0, INT_MAX); 727 728 /* draw the line numbers if exposed area includes them */ 729 if (textD->lineNumWidth != 0 && left <= textD->lineNumLeft + textD->lineNumWidth) { 730 redrawLineNumbers(textD, top, height, True); 731 } 732 } 733 734 /* 735 ** Refresh all of the text between buffer positions "start" and "end" 736 ** not including the character at the position "end". 737 ** If end points beyond the end of the buffer, refresh the whole display 738 ** after pos, including blank lines which are not technically part of 739 ** any range of characters. 740 */ 741 static void textDRedisplayRange(textDisp *textD, int start, int end) 742 { 743 int i, startLine, lastLine, startIndex, endIndex; 744 745 /* If the range is outside of the displayed text, just return */ 746 if (end < textD->firstChar || (start > textD->lastChar && 747 !emptyLinesVisible(textD))) 748 return; 749 750 /* Clean up the starting and ending values */ 751 if (start < 0) start = 0; 752 if (start > textD->buffer->length) start = textD->buffer->length; 753 if (end < 0) end = 0; 754 if (end > textD->buffer->length) end = textD->buffer->length; 755 756 /* Get the starting and ending lines */ 757 if (start < textD->firstChar) { 758 start = textD->firstChar; 759 } 760 761 if (!posToVisibleLineNum(textD, start, &startLine)) { 762 startLine = textD->nVisibleLines - 1; 763 } 764 765 if (end >= textD->lastChar) { 766 lastLine = textD->nVisibleLines - 1; 767 } else { 768 if (!posToVisibleLineNum(textD, end, &lastLine)) { 769 /* shouldn't happen */ 770 lastLine = textD->nVisibleLines - 1; 771 } 772 } 773 774 /* Get the starting and ending positions within the lines */ 775 startIndex = (textD->lineStarts[startLine] == -1) 776 ? 0 777 : start - textD->lineStarts[startLine]; 778 if (end >= textD->lastChar) 779 { 780 /* Request to redisplay beyond textD->lastChar, so tell 781 redisplayLine() to display everything to infy. */ 782 endIndex = INT_MAX; 783 } else if (textD->lineStarts[lastLine] == -1) 784 { 785 /* Here, lastLine is determined by posToVisibleLineNum() (see 786 if/else above) but deemed to be out of display according to 787 textD->lineStarts. */ 788 endIndex = 0; 789 } else 790 { 791 endIndex = end - textD->lineStarts[lastLine]; 792 } 793 794 /* Reset the clipping rectangles for the drawing GCs which are shared 795 using XtAllocateGC, and may have changed since the last use */ 796 resetClipRectangles(textD); 797 798 /* If the starting and ending lines are the same, redisplay the single 799 line between "start" and "end" */ 800 if (startLine == lastLine) { 801 redisplayLine(textD, startLine, 0, INT_MAX, startIndex, endIndex); 802 return; 803 } 804 805 /* Redisplay the first line from "start" */ 806 redisplayLine(textD, startLine, 0, INT_MAX, startIndex, INT_MAX); 807 808 /* Redisplay the lines in between at their full width */ 809 for (i=startLine+1; i<lastLine; i++) 810 redisplayLine(textD, i, 0, INT_MAX, 0, INT_MAX); 811 812 /* Redisplay the last line to "end" */ 813 redisplayLine(textD, lastLine, 0, INT_MAX, 0, endIndex); 814 } 815 816 /* 817 ** Set the scroll position of the text display vertically by line number and 818 ** horizontally by pixel offset from the left margin 819 */ 820 void TextDSetScroll(textDisp *textD, int topLineNum, int horizOffset) 821 { 822 int sliderSize, sliderMax; 823 int vPadding = (int)(TEXT_OF_TEXTD(textD).cursorVPadding); 824 825 /* Limit the requested scroll position to allowable values */ 826 if (topLineNum < 1) 827 topLineNum = 1; 828 else if ((topLineNum > textD->topLineNum) && 829 (topLineNum > (textD->nBufferLines + 2 - textD->nVisibleLines + 830 vPadding))) 831 topLineNum = max(textD->topLineNum, 832 textD->nBufferLines + 2 - textD->nVisibleLines + vPadding); 833 XtVaGetValues(textD->hScrollBar, XmNmaximum, &sliderMax, 834 XmNsliderSize, &sliderSize, NULL); 835 if (horizOffset < 0) 836 horizOffset = 0; 837 if (horizOffset > sliderMax - sliderSize) 838 horizOffset = sliderMax - sliderSize; 839 840 setScroll(textD, topLineNum, horizOffset, True, True); 841 } 842 843 /* 844 ** Get the current scroll position for the text display, in terms of line 845 ** number of the top line and horizontal pixel offset from the left margin 846 */ 847 void TextDGetScroll(textDisp *textD, int *topLineNum, int *horizOffset) 848 { 849 *topLineNum = textD->topLineNum; 850 *horizOffset = textD->horizOffset; 851 } 852 853 /* 854 ** Set the position of the text insertion cursor for text display "textD" 855 */ 856 void TextDSetInsertPosition(textDisp *textD, int newPos) 857 { 858 int oldLineStart, newLineStart, oldLineEnd, newLineEnd; 859 Boolean hiline = False; 860 if(textD->highlightCursorLine) { 861 oldLineStart = BufStartOfLine(textD->buffer, textD->cursor->cursorPos); 862 newLineStart = BufStartOfLine(textD->buffer, newPos); 863 if(oldLineStart != newLineStart || textD->mcursorOn || textD->redrawCursorLine) { 864 hiline = True; 865 oldLineEnd = BufEndOfLine(textD->buffer, textD->cursor->cursorPos); 866 newLineEnd = BufEndOfLine(textD->buffer, newPos); 867 textD->cursor->cursorPos = -1; 868 } 869 } 870 871 Boolean redrawTextWidget = 0; 872 if(TextDClearMultiCursor(textD)) { 873 redrawTextWidget = True; 874 textD->cursor = textD->multicursor; 875 } else if (newPos == textD->cursor->cursorPos) { 876 /* do nothing if it hasn't changed */ 877 return; 878 } 879 880 /* make sure new position is ok */ 881 if (newPos < 0) newPos = 0; 882 if (newPos > textD->buffer->length) newPos = textD->buffer->length; 883 884 /* cursor movement cancels vertical cursor motion column */ 885 textD->cursor->cursorPreferredCol = -1; 886 887 /* erase the cursor at it's previous position */ 888 TextDBlankCursor(textD); 889 890 /* draw it at its new position */ 891 textD->cursor->cursorPos = newPos; 892 textD->cursorOn = True; 893 894 if(redrawTextWidget) { 895 // redraw everything 896 TextDRedisplayRect(textD, 0, textD->top, textD->width + textD->left, textD->height); 897 return; 898 } 899 900 int left, right; 901 TextDCursorLR(textD, &left, &right); 902 textDRedisplayRange(textD, left, right); 903 904 if(hiline) { 905 int oldLine = -1, newLine = -1, oldLine2 = -1, newLine2 = -1; 906 907 posToVisibleLineNum(textD, newLineStart, &newLine); 908 posToVisibleLineNum(textD, newLineEnd, &newLine2); 909 910 if(newLine == -1 && newLine2 >= 0) { 911 newLine = 0; 912 } 913 if(newLine2 == -1 && newLine >= 0) { 914 newLine2 = textD->nVisibleLines-1; 915 } 916 917 posToVisibleLineNum(textD, oldLineStart, &oldLine); 918 posToVisibleLineNum(textD, oldLineEnd, &oldLine2); 919 920 if(oldLine == -1 && oldLine2 >= 0) { 921 oldLine = 0; 922 } 923 if(oldLine2 == -1 && oldLine >= 0) { 924 oldLine2 = textD->nVisibleLines-1; 925 } 926 927 for(int i=oldLine;i<=oldLine2;i++) { 928 redisplayLine(textD, i, 0, INT_MAX, 0, INT_MAX); 929 } 930 931 for(int i=newLine;i<=newLine2;i++) { 932 redisplayLine(textD, i, 0, INT_MAX, 0, INT_MAX); 933 } 934 } 935 } 936 937 /* 938 * Add diff to all cursors >= startPos 939 */ 940 void TextDChangeCursors(textDisp *textD, int startPos, int diff) { 941 //int prevPos = -2; 942 size_t newMCursorSize = textD->mcursorSize; 943 for(int i=textD->mcursorSize-1;i>=0;i--) { 944 if(textD->multicursor[i].cursorPos < startPos) { 945 break; 946 } 947 textD->multicursor[i].cursorPos += diff; 948 textD->multicursor[i].cursorPreferredCol = -1; 949 950 // cursor out of bounds: remove cursor 951 if(textD->multicursor[i].cursorPos > textD->buffer->length) { 952 newMCursorSize = i; 953 } 954 } 955 textD->mcursorSize = newMCursorSize > 0 ? newMCursorSize : 1; 956 textD->mcursorSizeReal = textD->mcursorSize; 957 if(textD->mcursorSize == 1) { 958 textD->mcursorOn = False; 959 } 960 } 961 962 int TextDAddCursor(textDisp *textD, int newMultiCursorPos) { 963 int mcInsertPos = 0; 964 965 // make sure, there is not already a cursor for the new position 966 for(int i=0;i<textD->mcursorSize;i++) { 967 if(textD->multicursor[i].cursorPos == newMultiCursorPos) { 968 return i; // pos already in the cursor pos array 969 } else if(textD->multicursor[i].cursorPos > newMultiCursorPos) { 970 break; 971 } 972 mcInsertPos = i+1; 973 } 974 975 // check limit 976 if(textD->mcursorSize == MCURSOR_MAX) { 977 return -1; 978 } 979 980 // check array size, do we need to realloc the array? 981 if(textD->mcursorSize == textD->mcursorAlloc) { 982 textD->mcursorAlloc *= 2; 983 textD->multicursor = NEditRealloc(textD->multicursor, textD->mcursorAlloc * sizeof(textCursor)); 984 } 985 986 textD->mcursorOn = TRUE; 987 988 // add cursor (sorted) 989 textD->mcursorSize++; 990 textD->mcursorSizeReal = textD->mcursorSize; 991 if(mcInsertPos+1 < textD->mcursorSize) { 992 // insert pos in the middle 993 // move elements one position 994 memmove(textD->multicursor+mcInsertPos+1, textD->multicursor+mcInsertPos, (textD->mcursorSize-mcInsertPos-1)*sizeof(textCursor)); 995 } 996 textCursor newCursor = TextDPos2Cursor(textD, newMultiCursorPos); 997 textD->multicursor[mcInsertPos] = newCursor; 998 textD->newcursor = &textD->multicursor[mcInsertPos]; 999 1000 textD->cursor = textD->multicursor; 1001 1002 // render new cursor 1003 textD->cursorOn = False; 1004 if(textD->highlightCursorLine) { 1005 // redraw entire line 1006 //int newCursorLineStart = BufStartOfLine(textD->buffer, newMultiCursorPos); 1007 int newCursorLine; 1008 posToVisibleLineNum(textD, newMultiCursorPos, &newCursorLine); 1009 redisplayLine(textD, newCursorLine, 0, INT_MAX, 0, INT_MAX); 1010 } 1011 TextDUnblankCursor(textD); 1012 1013 return -1; 1014 } 1015 1016 void TextDRemoveCursor(textDisp *textD, int cursorIndex) { 1017 if(textD->mcursorSize == 1) { 1018 return; 1019 } else if(textD->mcursorSize == 2) { 1020 textD->mcursorOn = FALSE; 1021 } 1022 1023 int cursorLine; 1024 posToVisibleLineNum(textD, textD->multicursor[cursorIndex].cursorPos, &cursorLine); 1025 1026 if(cursorIndex+1 != textD->mcursorSize) { 1027 memmove(textD->multicursor + cursorIndex, textD->multicursor + cursorIndex + 1, (textD->mcursorSize - cursorIndex - 1)*sizeof(textCursor)); 1028 } 1029 textD->mcursorSize--; 1030 textD->mcursorSizeReal = textD->mcursorSize; 1031 1032 if(textD->highlightCursorLine) { 1033 // redraw entire line 1034 redisplayLine(textD, cursorLine, 0, INT_MAX, 0, INT_MAX); 1035 } 1036 1037 textD->newcursor = textD->cursor; 1038 } 1039 1040 void TextDSetCursors(textDisp *textD, size_t *cursors, size_t ncursors) { 1041 TextDBlankCursor(textD); 1042 textD->mcursorSize = 0; 1043 for(int i=0;i<ncursors;i++) { 1044 TextDAddCursor(textD, (int)cursors[i]); 1045 } 1046 } 1047 1048 int TextDClearMultiCursor(textDisp *textD) { 1049 if(textD->mcursorSize > 1) { 1050 TextDBlankCursor(textD); 1051 textD->mcursorOn = FALSE; 1052 textD->mcursorSize = 1; 1053 textD->mcursorSizeReal = 1; 1054 if(textD->mcursorAlloc > MCURSOR_ALLOC_RESET) { 1055 // reduce multicursor array, if it is too big 1056 textD->mcursorAlloc = MCURSOR_ALLOC; 1057 textD->multicursor = NEditRealloc(textD->multicursor, MCURSOR_ALLOC * sizeof(textCursor)); 1058 } 1059 textD->cursor = textD->multicursor; 1060 textD->newcursor = textD->multicursor; 1061 return 1; 1062 } 1063 return 0; 1064 } 1065 1066 void TextDCheckCursorDuplicates(textDisp *textD) { 1067 size_t mcursorSize = textD->mcursorSize; 1068 for(size_t i=1;i<textD->mcursorSize;i++) { 1069 if(textD->multicursor[i].cursorPos == textD->multicursor[i-1].cursorPos) { 1070 TextDRemoveCursor(textD, i); 1071 mcursorSize--; 1072 i--; 1073 } 1074 } 1075 textD->cursor = textD->multicursor; 1076 textD->newcursor = textD->multicursor; 1077 1078 if(textD->highlightCursorLine) { 1079 size_t lastPos = textD->multicursor[textD->mcursorSize-1].cursorPos; 1080 int cursorLine; 1081 posToVisibleLineNum(textD, lastPos, &cursorLine); 1082 1083 if(cursorLine + 1 < textD->nVisibleLines) { 1084 redisplayLine(textD, cursorLine + 1, 0, INT_MAX, 0, INT_MAX); 1085 } 1086 } 1087 } 1088 1089 static void textDBlankCursorPos(textDisp *textD) { 1090 blankSingleCursorProtrusions(textD); 1091 textD->cursorOn = False; 1092 int left, right; 1093 TextDCursorLR(textD, &left, &right); 1094 textDRedisplayRange(textD, left, right); 1095 } 1096 1097 void TextDBlankCursor(textDisp *textD) 1098 { 1099 if (!textD->cursorOn) 1100 return; 1101 1102 if(textD->mcursorSize == 1) { 1103 textDBlankCursorPos(textD); 1104 } else { 1105 textCursor *origCursor = textD->cursor; 1106 for(int i=0;i<textD->mcursorSize;i++) { 1107 textD->cursor = textD->multicursor + i; 1108 textDBlankCursorPos(textD); 1109 } 1110 textD->cursor = origCursor; 1111 } 1112 } 1113 1114 void textDUnblankCursorPos(textDisp *textD) { 1115 int left, right; 1116 TextDCursorLR(textD, &left, &right); 1117 textDRedisplayRange(textD, left, right); 1118 } 1119 1120 void TextDUnblankCursor(textDisp *textD) 1121 { 1122 if (!textD->cursorOn) { 1123 textD->cursorOn = True; 1124 if(textD->mcursorSize == 1) { 1125 textDUnblankCursorPos(textD); 1126 } else { 1127 textCursor *origCursor = textD->cursor; 1128 for(int i=0;i<textD->mcursorSize;i++) { 1129 textD->cursor = textD->multicursor + i; 1130 textDUnblankCursorPos(textD); 1131 } 1132 textD->cursor = origCursor; 1133 } 1134 } 1135 } 1136 1137 void TextDSetCursorStyle(textDisp *textD, int style) 1138 { 1139 textD->cursorStyle = style; 1140 blankCursorProtrusions(textD); 1141 if (textD->cursorOn) { 1142 int left, right; 1143 if(textD->mcursorSize == 1) { 1144 TextDCursorLR(textD, &left, &right); 1145 textDRedisplayRange(textD, left, right); 1146 } else { 1147 textCursor *origCursor = textD->cursor; 1148 for(int i=0;i<textD->mcursorSize;i++) { 1149 textD->cursor = textD->multicursor + i; 1150 TextDCursorLR(textD, &left, &right); 1151 textDRedisplayRange(textD, left, right); 1152 } 1153 textD->cursor = origCursor; 1154 } 1155 } 1156 } 1157 1158 Boolean TextDPosHasCursor(textDisp *textD, int pos, int *index) { 1159 *index = -1; 1160 if(textD->mcursorSizeReal == 1) { 1161 return pos == textD->cursor->cursorPos; 1162 } else { 1163 for(int i=0;i<textD->mcursorSizeReal;i++) { 1164 int cursor = textD->multicursor[i].cursorPos; 1165 if(pos == cursor) { 1166 *index = i; 1167 return True; 1168 } 1169 } 1170 return False; 1171 } 1172 } 1173 1174 Boolean TextDRangeHasCursor(textDisp *textD, int start, int end) { 1175 if(textD->mcursorSizeReal == 1) { 1176 int cursor = textD->cursor->cursorPos; 1177 return cursor >= start && cursor <= end; 1178 } else { 1179 for(int i=0;i<textD->mcursorSizeReal;i++) { 1180 int cursor = textD->multicursor[i].cursorPos; 1181 if(cursor >= start && cursor <= end) { 1182 return True; 1183 } 1184 } 1185 return False; 1186 } 1187 } 1188 1189 void TextDSetWrapMode(textDisp *textD, int wrap, int wrapMargin) 1190 { 1191 textD->wrapMargin = wrapMargin; 1192 textD->continuousWrap = wrap; 1193 textD->cacheNoWrapping = False; 1194 1195 /* wrapping can change change the total number of lines, re-count */ 1196 Boolean retWrap; 1197 textD->nBufferLines = TextDCountLinesW(textD, 0, textD->buffer->length, 1198 True, &retWrap); 1199 if(!retWrap) { 1200 textD->cacheNoWrappingWidth = textD->width; 1201 textD->cacheNoWrapping = True; 1202 } 1203 1204 /* changing wrap margins wrap or changing from wrapped mode to non-wrapped 1205 can leave the character at the top no longer at a line start, and/or 1206 change the line number */ 1207 textD->firstChar = TextDStartOfLine(textD, textD->firstChar); 1208 textD->topLineNum = TextDCountLines(textD, 0, textD->firstChar, True) + 1; 1209 resetAbsLineNum(textD); 1210 1211 /* update the line starts array */ 1212 calcLineStarts(textD, 0, textD->nVisibleLines); 1213 calcLastChar(textD); 1214 1215 /* Update the scroll bar page increment size (as well as other scroll 1216 bar parameters) */ 1217 updateVScrollBarRange(textD); 1218 updateHScrollBarRange(textD); 1219 1220 /* Decide if the horizontal scroll bar needs to be visible */ 1221 hideOrShowHScrollBar(textD); 1222 1223 /* Do a full redraw */ 1224 TextDRedisplayRect(textD, 0, textD->top, textD->width + textD->left, 1225 textD->height); 1226 } 1227 1228 int TextDGetInsertPosition(textDisp *textD) 1229 { 1230 return textD->cursor->cursorPos; 1231 } 1232 1233 /* 1234 ** Insert "text" at the current cursor location. This has the same 1235 ** effect as inserting the text into the buffer using BufInsert and 1236 ** then moving the insert position after the newly inserted text, except 1237 ** that it's optimized to do less redrawing. 1238 */ 1239 void TextDInsert(textDisp *textD, char *text) 1240 { 1241 int pos = textD->cursor->cursorPos; 1242 1243 textD->cursorToHint = pos + strlen(text); 1244 BufInsert(textD->buffer, pos, text); 1245 textD->cursorToHint = NO_HINT; 1246 } 1247 1248 /* 1249 ** Insert "text" (which must not contain newlines), overstriking the current 1250 ** cursor location. 1251 */ 1252 void TextDOverstrike(textDisp *textD, char *text) 1253 { 1254 int startPos = textD->cursor->cursorPos; 1255 textBuffer *buf = textD->buffer; 1256 int lineStart = BufStartOfLine(buf, startPos); 1257 int textLen = strlen(text); 1258 int i, p, endPos, indent, startIndent, endIndent, inc; 1259 char *c, ch, *paddedText = NULL; 1260 1261 /* determine how many displayed character positions are covered */ 1262 startIndent = BufCountDispChars(textD->buffer, lineStart, startPos); 1263 indent = startIndent; 1264 for (c=text; *c!='\0'; c+=inc) { 1265 inc = Utf8CharLen((unsigned char*)c); 1266 if(inc >= 1) { 1267 indent++; 1268 } else { 1269 indent += BufCharWidth(*c, indent, buf->tabDist, buf->nullSubsChar); 1270 } 1271 } 1272 endIndent = indent; 1273 1274 /* find which characters to remove, and if necessary generate additional 1275 padding to make up for removed control characters at the end */ 1276 indent=startIndent; 1277 for (p=startPos; ; p+=inc) { 1278 if (p == buf->length) 1279 break; 1280 ch = BufGetCharacter(buf, p); 1281 inc = Utf8CharLen((unsigned char*)&ch); 1282 if (ch == '\n') 1283 break; 1284 indent += BufCharWidth(ch, indent, buf->tabDist, buf->nullSubsChar); 1285 if (indent == endIndent) { 1286 p += inc; 1287 break; 1288 } else if (indent > endIndent) { 1289 if (ch != '\t') { 1290 p += inc; 1291 paddedText = (char*)NEditMalloc(textLen + MAX_EXP_CHAR_LEN + 1); 1292 strcpy(paddedText, text); 1293 for (i=0; i<indent-endIndent; i++) 1294 paddedText[textLen+i] = ' '; 1295 paddedText[textLen+i] = '\0'; 1296 } 1297 break; 1298 } 1299 } 1300 endPos = p; 1301 1302 textD->cursorToHint = startPos + textLen; 1303 BufReplace(buf, startPos, endPos, paddedText == NULL ? text : paddedText); 1304 textD->cursorToHint = NO_HINT; 1305 NEditFree(paddedText); 1306 } 1307 1308 1309 XftColor PixelToColor(Widget w, Pixel p) 1310 { 1311 XColor xcolor; 1312 memset(&xcolor, 0, sizeof(XColor)); 1313 xcolor.pixel = p; 1314 XQueryColor(XtDisplay(w), w->core.colormap, &xcolor); 1315 1316 XftColor color; 1317 color.pixel = p; 1318 color.color.red = xcolor.red; 1319 color.color.green = xcolor.green; 1320 color.color.blue = xcolor.blue; 1321 color.color.alpha = 0xFFFF; 1322 return color; 1323 } 1324 1325 /* 1326 XftColor RGBToColor(short r, short g, short b) 1327 { 1328 XftColor color; 1329 color.color.red = r; 1330 color.color.green = g; 1331 color.color.blue = b; 1332 color.color.alpha = 0xFFFF; 1333 return color; 1334 } 1335 */ 1336 1337 /* 1338 ** Translate window coordinates to the nearest text cursor position. 1339 */ 1340 int TextDXYToPosition(textDisp *textD, int x, int y) 1341 { 1342 return xyToPos(textD, x, y, CURSOR_POS); 1343 } 1344 1345 /* 1346 ** Translate window coordinates to the nearest character cell. 1347 */ 1348 int TextDXYToCharPos(textDisp *textD, int x, int y) 1349 { 1350 return xyToPos(textD, x, y, CHARACTER_POS); 1351 } 1352 1353 /* 1354 ** Translate window coordinates to the nearest row and column number for 1355 ** positioning the cursor. This, of course, makes no sense when the font 1356 ** is proportional, since there are no absolute columns. 1357 */ 1358 void TextDXYToUnconstrainedPosition(textDisp *textD, int x, int y, int *row, 1359 int *column) 1360 { 1361 xyToUnconstrainedPos(textD, x, y, row, column, CURSOR_POS); 1362 } 1363 1364 /* 1365 ** Translate line and column to the nearest row and column number for 1366 ** positioning the cursor. This, of course, makes no sense when the font 1367 ** is proportional, since there are no absolute columns. 1368 */ 1369 int TextDLineAndColToPos(textDisp *textD, int lineNum, int column) 1370 { 1371 int i, lineEnd, charIndex, outIndex, isMB; 1372 int lineStart=0, charLen=0; 1373 char expandedChar[MAX_EXP_CHAR_LEN]; 1374 1375 /* Count lines */ 1376 if (lineNum < 1) 1377 lineNum = 1; 1378 lineEnd = -1; 1379 for (i=1; i<=lineNum && lineEnd<textD->buffer->length; i++) { 1380 lineStart = lineEnd + 1; 1381 lineEnd = BufEndOfLine(textD->buffer, lineStart); 1382 } 1383 1384 /* If line is beyond end of buffer, position at last character in buffer */ 1385 if ( lineNum >= i ) { 1386 return lineEnd; 1387 } 1388 1389 /* Start character index at zero */ 1390 charIndex=0; 1391 1392 /* Only have to count columns if column isn't zero (or negative) */ 1393 if (column > 0) { 1394 /* Count columns, expanding each character */ 1395 char *lineStrAlloc; 1396 const char *lineStr = BufGetRange2(textD->buffer, lineStart, lineEnd, &lineStrAlloc); 1397 outIndex = 0; 1398 for(i=lineStart; i<lineEnd; i++, charIndex++) { 1399 charLen = BufExpandCharacter(lineStr+charIndex, lineEnd-charIndex, 1400 outIndex, expandedChar, textD->buffer->tabDist, 1401 textD->buffer->nullSubsChar, &isMB); 1402 if ( outIndex+charLen >= column ) break; 1403 outIndex+=charLen; 1404 } 1405 NEditFree(lineStrAlloc); 1406 1407 /* If the column is in the middle of an expanded character, put cursor 1408 * in front of character if in first half of character, and behind 1409 * character if in last half of character 1410 */ 1411 if (column >= outIndex + ( charLen / 2 )) 1412 charIndex++; 1413 1414 /* If we are beyond the end of the line, back up one space */ 1415 if ((i>=lineEnd)&&(charIndex>0)) charIndex--; 1416 } 1417 1418 /* Position is the start of the line plus the index into line buffer */ 1419 return lineStart + charIndex; 1420 } 1421 1422 /* 1423 ** Translate a buffer text position to the XY location where the center 1424 ** of the cursor would be positioned to point to that character. Returns 1425 ** False if the position is not displayed because it is VERTICALLY out 1426 ** of view. If the position is horizontally out of view, returns the 1427 ** x coordinate where the position would be if it were visible. 1428 */ 1429 int TextDPositionToXY(textDisp *textD, int pos, int *x, int *y) 1430 { 1431 int charIndex, lineStartPos, fontHeight, lineLen; 1432 int visLineNum, charLen, outIndex, xStep, charStyle, inc; 1433 FcChar32 expandedChar[MAX_EXP_CHAR_LEN]; 1434 FcChar32 uc; 1435 NFont *font; 1436 1437 /* If position is not displayed, return false */ 1438 if (pos < textD->firstChar || 1439 (pos > textD->lastChar && !emptyLinesVisible(textD))) 1440 return False; 1441 1442 /* Calculate y coordinate */ 1443 if (!posToVisibleLineNum(textD, pos, &visLineNum)) 1444 return False; 1445 fontHeight = textD->ascent + textD->descent; 1446 *y = textD->top + visLineNum*fontHeight + fontHeight/2; 1447 1448 /* Get the text, length, and buffer position of the line. If the position 1449 is beyond the end of the buffer and should be at the first position on 1450 the first empty line, don't try to get or scan the text */ 1451 lineStartPos = textD->lineStarts[visLineNum]; 1452 if (lineStartPos == -1) { 1453 *x = textD->left - textD->horizOffset; 1454 return True; 1455 } 1456 lineLen = visLineLength(textD, visLineNum); 1457 char *lineStrAlloc; 1458 const char *lineStr = BufGetRange2(textD->buffer, lineStartPos, lineStartPos + lineLen, &lineStrAlloc); 1459 1460 /* Step through character positions from the beginning of the line 1461 to "pos" to calculate the x coordinate */ 1462 xStep = textD->left - textD->horizOffset; 1463 outIndex = 0; 1464 for(charIndex=0; charIndex<pos-lineStartPos; charIndex+=inc) { 1465 inc = getCharWidth(textD, lineStr+charIndex, &uc, lineLen - charIndex); 1466 if(inc > 1) { 1467 charLen = 1; 1468 expandedChar[0] = uc; 1469 } else { 1470 charLen = BufExpandCharacter4(lineStr[charIndex], 1471 outIndex, 1472 expandedChar, 1473 textD->buffer->tabDist, textD->buffer->nullSubsChar); 1474 } 1475 1476 charStyle = styleOfPos(textD, lineStartPos, lineLen, charIndex, 1477 outIndex, lineStr[charIndex]); 1478 font = styleFontList(textD, charStyle); 1479 xStep += charWidth4(textD, expandedChar, charLen, font); 1480 outIndex += charLen; 1481 } 1482 *x = xStep; 1483 NEditFree(lineStrAlloc); 1484 return True; 1485 } 1486 1487 /* 1488 ** If the text widget is maintaining a line number count appropriate to "pos" 1489 ** return the line and column numbers of pos, otherwise return False. If 1490 ** continuous wrap mode is on, returns the absolute line number (as opposed to 1491 ** the wrapped line number which is used for scrolling). THIS ROUTINE ONLY 1492 ** WORKS FOR DISPLAYED LINES AND, IN CONTINUOUS WRAP MODE, ONLY WHEN THE 1493 ** ABSOLUTE LINE NUMBER IS BEING MAINTAINED. Otherwise, it returns False. 1494 */ 1495 int TextDPosToLineAndCol(textDisp *textD, int pos, int *lineNum, int *column) 1496 { 1497 textBuffer *buf = textD->buffer; 1498 1499 /* In continuous wrap mode, the absolute (non-wrapped) line count is 1500 maintained separately, as needed. Only return it if we're actually 1501 keeping track of it and pos is in the displayed text */ 1502 if (textD->continuousWrap) { 1503 if (!maintainingAbsTopLineNum(textD) || pos < textD->firstChar || 1504 pos > textD->lastChar) 1505 return False; 1506 *lineNum = textD->absTopLineNum + BufCountLines(buf, 1507 textD->firstChar, pos); 1508 *column = BufCountDispChars(buf, BufStartOfLine(buf, pos), pos); 1509 return True; 1510 } 1511 1512 /* Only return the data if pos is within the displayed text */ 1513 if (!posToVisibleLineNum(textD, pos, lineNum)) 1514 return False; 1515 *column = BufCountDispChars(buf, textD->lineStarts[*lineNum], pos); 1516 *lineNum += textD->topLineNum; 1517 return True; 1518 } 1519 1520 /* 1521 ** Return True if position (x, y) is inside of the primary selection 1522 */ 1523 int TextDInSelection(textDisp *textD, int x, int y) 1524 { 1525 int row, column, pos = xyToPos(textD, x, y, CHARACTER_POS); 1526 textBuffer *buf = textD->buffer; 1527 1528 xyToUnconstrainedPos(textD, x, y, &row, &column, CHARACTER_POS); 1529 if (rangeTouchesRectSel(&buf->primary, textD->firstChar, textD->lastChar)) 1530 column = TextDOffsetWrappedColumn(textD, row, column); 1531 return inSelection(&buf->primary, pos, BufStartOfLine(buf, pos), column); 1532 } 1533 1534 /* 1535 ** Correct a column number based on an unconstrained position (as returned by 1536 ** TextDXYToUnconstrainedPosition) to be relative to the last actual newline 1537 ** in the buffer before the row and column position given, rather than the 1538 ** last line start created by line wrapping. This is an adapter 1539 ** for rectangular selections and code written before continuous wrap mode, 1540 ** which thinks that the unconstrained column is the number of characters 1541 ** from the last newline. Obviously this is time consuming, because it 1542 ** invloves character re-counting. 1543 */ 1544 int TextDOffsetWrappedColumn(textDisp *textD, int row, int column) 1545 { 1546 int lineStart, dispLineStart; 1547 1548 if (!textD->continuousWrap || row < 0 || row > textD->nVisibleLines) 1549 return column; 1550 dispLineStart = textD->lineStarts[row]; 1551 if (dispLineStart == -1) 1552 return column; 1553 lineStart = BufStartOfLine(textD->buffer, dispLineStart); 1554 return column + BufCountDispChars(textD->buffer, lineStart, dispLineStart); 1555 } 1556 1557 /* 1558 ** Correct a row number from an unconstrained position (as returned by 1559 ** TextDXYToUnconstrainedPosition) to a straight number of newlines from the 1560 ** top line of the display. Because rectangular selections are based on 1561 ** newlines, rather than display wrapping, and anywhere a rectangular selection 1562 ** needs a row, it needs it in terms of un-wrapped lines. 1563 */ 1564 int TextDOffsetWrappedRow(textDisp *textD, int row) 1565 { 1566 if (!textD->continuousWrap || row < 0 || row > textD->nVisibleLines) 1567 return row; 1568 return BufCountLines(textD->buffer, textD->firstChar, 1569 textD->lineStarts[row]); 1570 } 1571 1572 /* 1573 ** Scroll the display to bring insertion cursor into view. 1574 ** 1575 ** Note: it would be nice to be able to do this without counting lines twice 1576 ** (setScroll counts them too) and/or to count from the most efficient 1577 ** starting point, but the efficiency of this routine is not as important to 1578 ** the overall performance of the text display. 1579 */ 1580 void TextDMakeInsertPosVisible(textDisp *textD) 1581 { 1582 int hOffset, topLine, x, y; 1583 int cursorPos = textD->cursor->cursorPos; 1584 int linesFromTop = 0, do_padding = 1; 1585 int cursorVPadding = (int)TEXT_OF_TEXTD(textD).cursorVPadding; 1586 1587 hOffset = textD->horizOffset; 1588 topLine = textD->topLineNum; 1589 1590 /* Don't do padding if this is a mouse operation */ 1591 do_padding = ((TEXT_OF_TEXTD(textD).dragState == NOT_CLICKED) && 1592 (cursorVPadding > 0)); 1593 1594 /* Find the new top line number */ 1595 if (cursorPos < textD->firstChar) { 1596 topLine -= TextDCountLines(textD, cursorPos, textD->firstChar, False); 1597 /* linesFromTop = 0; */ 1598 } else if (cursorPos > textD->lastChar && !emptyLinesVisible(textD)) { 1599 topLine += TextDCountLines(textD, textD->lastChar - 1600 (wrapUsesCharacter(textD, textD->lastChar) ? 0 : 1), 1601 cursorPos, False); 1602 linesFromTop = textD->nVisibleLines-1; 1603 } else if (cursorPos == textD->lastChar && !emptyLinesVisible(textD) && 1604 !wrapUsesCharacter(textD, textD->lastChar)) { 1605 topLine++; 1606 linesFromTop = textD->nVisibleLines-1; 1607 } else { 1608 /* Avoid extra counting if cursorVPadding is disabled */ 1609 if (do_padding) 1610 linesFromTop = TextDCountLines(textD, textD->firstChar, 1611 cursorPos, True); 1612 } 1613 if (topLine < 1) { 1614 fprintf(stderr, "xnedit: internal consistency check tl1 failed\n"); 1615 topLine = 1; 1616 } 1617 1618 if (do_padding) { 1619 /* Keep the cursor away from the top or bottom of screen. */ 1620 if (textD->nVisibleLines <= 2*(int)cursorVPadding) { 1621 topLine += (linesFromTop - textD->nVisibleLines/2); 1622 topLine = max(topLine, 1); 1623 } else if (linesFromTop < (int)cursorVPadding) { 1624 topLine -= (cursorVPadding - linesFromTop); 1625 topLine = max(topLine, 1); 1626 } else if (linesFromTop > textD->nVisibleLines-(int)cursorVPadding-1) { 1627 topLine += (linesFromTop - (textD->nVisibleLines-cursorVPadding-1)); 1628 } 1629 } 1630 1631 /* Find the new setting for horizontal offset (this is a bit ungraceful). 1632 If the line is visible, just use TextDPositionToXY to get the position 1633 to scroll to, otherwise, do the vertical scrolling first, then the 1634 horizontal */ 1635 if (!TextDPositionToXY(textD, cursorPos, &x, &y)) { 1636 setScroll(textD, topLine, hOffset, True, True); 1637 if (!TextDPositionToXY(textD, cursorPos, &x, &y)) 1638 return; /* Give up, it's not worth it (but why does it fail?) */ 1639 } 1640 if (x > textD->left + textD->width) 1641 hOffset += x - (textD->left + textD->width); 1642 else if (x < textD->left) 1643 hOffset += x - textD->left; 1644 1645 /* Do the scroll */ 1646 setScroll(textD, topLine, hOffset, True, True); 1647 } 1648 1649 /* 1650 ** Return the current preferred column along with the current 1651 ** visible line index (-1 if not visible) and the lineStartPos 1652 ** of the current insert position. 1653 */ 1654 int TextDPreferredColumn(textDisp *textD, int *visLineNum, int *lineStartPos) 1655 { 1656 int column; 1657 1658 /* Find the position of the start of the line. Use the line starts array 1659 if possible, to avoid unbounded line-counting in continuous wrap mode */ 1660 if (posToVisibleLineNum(textD, textD->cursor->cursorPos, visLineNum)) { 1661 *lineStartPos = textD->lineStarts[*visLineNum]; 1662 } 1663 else { 1664 *lineStartPos = TextDStartOfLine(textD, textD->cursor->cursorPos); 1665 *visLineNum = -1; 1666 } 1667 1668 /* Decide what column to move to, if there's a preferred column use that */ 1669 column = (textD->cursor->cursorPreferredCol >= 0) 1670 ? textD->cursor->cursorPreferredCol 1671 : BufCountDispChars(textD->buffer, *lineStartPos, textD->cursor->cursorPos); 1672 return(column); 1673 } 1674 1675 /* 1676 ** Return the insert position of the requested column given 1677 ** the lineStartPos. 1678 */ 1679 int TextDPosOfPreferredCol(textDisp *textD, int column, int lineStartPos) 1680 { 1681 int newPos; 1682 1683 newPos = BufCountForwardDispChars(textD->buffer, lineStartPos, column); 1684 if (textD->continuousWrap) { 1685 newPos = min(newPos, TextDEndOfLine(textD, lineStartPos, True)); 1686 } 1687 return(newPos); 1688 } 1689 1690 /* 1691 ** Cursor movement functions 1692 */ 1693 int TextDMoveRight(textDisp *textD) 1694 { 1695 if (textD->cursor->cursorPos >= textD->buffer->length) 1696 return False; 1697 1698 TextDSetInsertPosition( 1699 textD, 1700 BufRightPos(textD->buffer, textD->cursor->cursorPos)); 1701 return True; 1702 } 1703 1704 int TextDMoveLeft(textDisp *textD) 1705 { 1706 if (textD->cursor->cursorPos <= 0) 1707 return False; 1708 1709 TextDSetInsertPosition(textD, BufLeftPos(textD->buffer, textD->cursor->cursorPos)); 1710 return True; 1711 } 1712 1713 int TextDMoveUp(textDisp *textD, int absolute) 1714 { 1715 int lineStartPos, column, prevLineStartPos, newPos, visLineNum; 1716 1717 /* Find the position of the start of the line. Use the line starts array 1718 if possible, to avoid unbounded line-counting in continuous wrap mode */ 1719 if (absolute) { 1720 lineStartPos = BufStartOfLine(textD->buffer, textD->cursor->cursorPos); 1721 visLineNum = -1; 1722 } else if (posToVisibleLineNum(textD, textD->cursor->cursorPos, &visLineNum)) 1723 lineStartPos = textD->lineStarts[visLineNum]; 1724 else { 1725 lineStartPos = TextDStartOfLine(textD, textD->cursor->cursorPos); 1726 visLineNum = -1; 1727 } 1728 if (lineStartPos == 0) 1729 return False; 1730 1731 /* Decide what column to move to, if there's a preferred column use that */ 1732 column = textD->cursor->cursorPreferredCol >= 0 1733 ? textD->cursor->cursorPreferredCol 1734 : BufCountDispChars(textD->buffer, lineStartPos, textD->cursor->cursorPos); 1735 1736 /* count forward from the start of the previous line to reach the column */ 1737 if (absolute) { 1738 prevLineStartPos = BufCountBackwardNLines(textD->buffer, lineStartPos, 1); 1739 } else if (visLineNum != -1 && visLineNum != 0) { 1740 prevLineStartPos = textD->lineStarts[visLineNum-1]; 1741 } else { 1742 prevLineStartPos = TextDCountBackwardNLines(textD, lineStartPos, 1); 1743 } 1744 1745 newPos = BufCountForwardDispChars(textD->buffer, prevLineStartPos, column); 1746 if (textD->continuousWrap && !absolute) 1747 newPos = min(newPos, TextDEndOfLine(textD, prevLineStartPos, True)); 1748 1749 /* move the cursor */ 1750 TextDSetInsertPosition(textD, newPos); 1751 1752 /* if a preferred column wasn't aleady established, establish it */ 1753 textD->cursor->cursorPreferredCol = column; 1754 1755 return True; 1756 } 1757 1758 int TextDMoveDown(textDisp *textD, int absolute) 1759 { 1760 int lineStartPos, column, nextLineStartPos, newPos, visLineNum; 1761 1762 if (textD->cursor->cursorPos == textD->buffer->length) { 1763 return False; 1764 } 1765 1766 if (absolute) { 1767 lineStartPos = BufStartOfLine(textD->buffer, textD->cursor->cursorPos); 1768 visLineNum = -1; 1769 } else if (posToVisibleLineNum(textD, textD->cursor->cursorPos, &visLineNum)) { 1770 lineStartPos = textD->lineStarts[visLineNum]; 1771 } else { 1772 lineStartPos = TextDStartOfLine(textD, textD->cursor->cursorPos); 1773 visLineNum = -1; 1774 } 1775 1776 column = textD->cursor->cursorPreferredCol >= 0 1777 ? textD->cursor->cursorPreferredCol 1778 : BufCountDispChars(textD->buffer, lineStartPos, textD->cursor->cursorPos); 1779 1780 if (absolute) 1781 nextLineStartPos = BufCountForwardNLines(textD->buffer, lineStartPos, 1); 1782 else 1783 nextLineStartPos = TextDCountForwardNLines(textD, lineStartPos, 1, True); 1784 1785 newPos = BufCountForwardDispChars(textD->buffer, nextLineStartPos, column); 1786 1787 if (textD->continuousWrap && !absolute) { 1788 newPos = min(newPos, TextDEndOfLine(textD, nextLineStartPos, True)); 1789 } 1790 1791 TextDSetInsertPosition(textD, newPos); 1792 textD->cursor->cursorPreferredCol = column; 1793 1794 return True; 1795 } 1796 1797 textCursor TextDPos2Cursor(textDisp *textD, int pos) { 1798 textBuffer *buf = textD->buffer; 1799 textCursor c; 1800 c.cursorPos = pos; 1801 c.cursorPosCache = pos; 1802 c.cursorPosCacheLeft = BufLeftPos(buf, pos); 1803 c.cursorPosCacheRight = BufRightPos(buf, pos); 1804 c.cursorPreferredCol = -1; 1805 c.x = -100; 1806 c.y = -100; 1807 if(textD->ansiColors) { 1808 while(BufGetCharacter(buf, c.cursorPosCacheLeft) == '\e') { 1809 c.cursorPosCacheLeft = BufLeftPos(buf, c.cursorPosCacheLeft); 1810 } 1811 while(BufGetCharacter(buf, pos) == '\e') { 1812 c.cursorPosCacheRight += BufCharLen(buf, c.cursorPosCacheRight); 1813 pos = c.cursorPosCacheRight; 1814 } 1815 c.cursorPosCacheRight += BufCharLen(buf, textD->cursor->cursorPosCacheRight); 1816 } 1817 return c; 1818 } 1819 1820 void TextDCursorLR(textDisp *textD, int *left, int *right) 1821 { 1822 if(textD->cursor->cursorPos != textD->cursor->cursorPosCache) { 1823 textCursor c = TextDPos2Cursor(textD, textD->cursor->cursorPos); 1824 textD->cursor->cursorPosCache = c.cursorPosCache; 1825 textD->cursor->cursorPosCacheLeft = c.cursorPosCacheLeft; 1826 textD->cursor->cursorPosCacheRight = c.cursorPosCacheRight; 1827 } 1828 *left = textD->cursor->cursorPosCacheLeft; 1829 *right = textD->cursor->cursorPosCacheRight; 1830 1831 } 1832 1833 void TextDSetAnsiColors(textDisp *textD, Boolean ansiColors) 1834 { 1835 textD->ansiColors = ansiColors; 1836 if(ansiColors) { 1837 BufEnableAnsiEsc(textD->buffer); 1838 textD->cursor->cursorPosCache = -1; 1839 } else { 1840 BufDisableAnsiEsc(textD->buffer); 1841 } 1842 } 1843 1844 /* 1845 ** Same as BufCountLines, but takes in to account wrapping if wrapping is 1846 ** turned on. If the caller knows that startPos is at a line start, it 1847 ** can pass "startPosIsLineStart" as True to make the call more efficient 1848 ** by avoiding the additional step of scanning back to the last newline. 1849 */ 1850 int TextDCountLines(textDisp *textD, int startPos, int endPos, 1851 int startPosIsLineStart) 1852 { 1853 Boolean retWrap; 1854 int retLines = TextDCountLinesW(textD, startPos, endPos, startPosIsLineStart, &retWrap); 1855 if(retWrap) { 1856 textD->cacheNoWrapping = False; 1857 } 1858 return retLines; 1859 } 1860 1861 int TextDCountLinesW(textDisp *textD, int startPos, int endPos, 1862 int startPosIsLineStart, Boolean *retWrapped) 1863 { 1864 int retLines, retPos, retLineStart, retLineEnd; 1865 1866 /* If we're not wrapping use simple (and more efficient) BufCountLines */ 1867 if (!textD->continuousWrap) { 1868 if(retWrapped) { 1869 *retWrapped = 0; 1870 } 1871 return BufCountLines(textD->buffer, startPos, endPos); 1872 } 1873 1874 wrappedLineCounter(textD, textD->buffer, startPos, endPos, INT_MAX, 1875 startPosIsLineStart, 0, &retPos, &retLines, &retLineStart, 1876 &retLineEnd, retWrapped); 1877 1878 return retLines; 1879 } 1880 1881 /* 1882 ** Same as BufCountForwardNLines, but takes in to account line breaks when 1883 ** wrapping is turned on. If the caller knows that startPos is at a line start, 1884 ** it can pass "startPosIsLineStart" as True to make the call more efficient 1885 ** by avoiding the additional step of scanning back to the last newline. 1886 */ 1887 int TextDCountForwardNLines(const textDisp* textD, int startPos, 1888 unsigned nLines, Boolean startPosIsLineStart) 1889 { 1890 int retLines, retPos, retLineStart, retLineEnd; 1891 1892 /* if we're not wrapping use more efficient BufCountForwardNLines */ 1893 if (!textD->continuousWrap) 1894 return BufCountForwardNLines(textD->buffer, startPos, nLines); 1895 1896 /* wrappedLineCounter can't handle the 0 lines case */ 1897 if (nLines == 0) 1898 return startPos; 1899 1900 /* use the common line counting routine to count forward */ 1901 wrappedLineCounter(textD, textD->buffer, startPos, textD->buffer->length, 1902 nLines, startPosIsLineStart, 0, &retPos, &retLines, &retLineStart, 1903 &retLineEnd, NULL); 1904 return retPos; 1905 } 1906 1907 /* 1908 ** Same as BufEndOfLine, but takes in to account line breaks when wrapping 1909 ** is turned on. If the caller knows that startPos is at a line start, it 1910 ** can pass "startPosIsLineStart" as True to make the call more efficient 1911 ** by avoiding the additional step of scanning back to the last newline. 1912 ** 1913 ** Note that the definition of the end of a line is less clear when continuous 1914 ** wrap is on. With continuous wrap off, it's just a pointer to the newline 1915 ** that ends the line. When it's on, it's the character beyond the last 1916 ** DISPLAYABLE character on the line, where a whitespace character which has 1917 ** been "converted" to a newline for wrapping is not considered displayable. 1918 ** Also note that, a line can be wrapped at a non-whitespace character if the 1919 ** line had no whitespace. In this case, this routine returns a pointer to 1920 ** the start of the next line. This is also consistent with the model used by 1921 ** visLineLength. 1922 */ 1923 int TextDEndOfLine(const textDisp* textD, int pos, 1924 Boolean startPosIsLineStart) 1925 { 1926 int retLines, retPos, retLineStart, retLineEnd; 1927 1928 /* If we're not wrapping use more efficient BufEndOfLine */ 1929 if (!textD->continuousWrap) 1930 return BufEndOfLine(textD->buffer, pos); 1931 1932 if (pos == textD->buffer->length) 1933 return pos; 1934 wrappedLineCounter(textD, textD->buffer, pos, textD->buffer->length, 1, 1935 startPosIsLineStart, 0, &retPos, &retLines, &retLineStart, 1936 &retLineEnd, NULL); 1937 return retLineEnd; 1938 } 1939 1940 /* 1941 ** Same as BufStartOfLine, but returns the character after last wrap point 1942 ** rather than the last newline. 1943 */ 1944 int TextDStartOfLine(const textDisp* textD, int pos) 1945 { 1946 int retLines, retPos, retLineStart, retLineEnd; 1947 1948 /* If we're not wrapping, use the more efficient BufStartOfLine */ 1949 if (!textD->continuousWrap) 1950 return BufStartOfLine(textD->buffer, pos); 1951 1952 wrappedLineCounter(textD, textD->buffer, BufStartOfLine(textD->buffer, pos), 1953 pos, INT_MAX, True, 0, &retPos, &retLines, &retLineStart, 1954 &retLineEnd, NULL); 1955 return retLineStart; 1956 } 1957 1958 /* 1959 ** Same as BufCountBackwardNLines, but takes in to account line breaks when 1960 ** wrapping is turned on. 1961 */ 1962 int TextDCountBackwardNLines(textDisp *textD, int startPos, int nLines) 1963 { 1964 textBuffer *buf = textD->buffer; 1965 int pos, lineStart, retLines, retPos, retLineStart, retLineEnd; 1966 1967 /* If we're not wrapping, use the more efficient BufCountBackwardNLines */ 1968 if (!textD->continuousWrap) 1969 return BufCountBackwardNLines(textD->buffer, startPos, nLines); 1970 1971 pos = startPos; 1972 while (True) { 1973 lineStart = BufStartOfLine(buf, pos); 1974 wrappedLineCounter(textD, textD->buffer, lineStart, pos, INT_MAX, 1975 True, 0, &retPos, &retLines, &retLineStart, &retLineEnd, 1976 NULL); 1977 if (retLines > nLines) 1978 return TextDCountForwardNLines(textD, lineStart, retLines-nLines, 1979 True); 1980 nLines -= retLines; 1981 pos = lineStart - 1; 1982 if (pos < 0) 1983 return 0; 1984 nLines -= 1; 1985 } 1986 } 1987 1988 /* 1989 ** Callback attached to the text buffer to receive delete information before 1990 ** the modifications are actually made. 1991 */ 1992 static void bufPreDeleteCB(int pos, int nDeleted, void *cbArg) 1993 { 1994 textDisp *textD = (textDisp *)cbArg; 1995 if (textD->continuousWrap && 1996 (textD->fixedFontWidth == -1 || textD->modifyingTabDist)) 1997 /* Note: we must perform this measurement, even if there is not a 1998 single character deleted; the number of "deleted" lines is the 1999 number of visual lines spanned by the real line in which the 2000 modification takes place. 2001 Also, a modification of the tab distance requires the same 2002 kind of calculations in advance, even if the font width is "fixed", 2003 because when the width of the tab characters changes, the layout 2004 of the text may be completely different. */ 2005 measureDeletedLines(textD, pos, nDeleted); 2006 else 2007 textD->suppressResync = 0; /* Probably not needed, but just in case */ 2008 } 2009 2010 /* 2011 ** Callback attached to the text buffer to receive modification information 2012 */ 2013 static void bufModifiedCB(int pos, int nInserted, int nDeleted, 2014 int nRestyled, const char *deletedText, void *cbArg) 2015 { 2016 int linesInserted, linesDeleted, startDispPos, endDispPos; 2017 textDisp *textD = (textDisp *)cbArg; 2018 textBuffer *buf = textD->buffer; 2019 int oldFirstChar = textD->firstChar; 2020 int scrolled, origCursorPos = textD->cursor->cursorPos; 2021 int wrapModStart, wrapModEnd; 2022 int redrawLN = False; 2023 2024 /* buffer modification cancels vertical cursor motion column */ 2025 if (nInserted != 0 || nDeleted != 0) 2026 textD->cursor->cursorPreferredCol = -1; 2027 2028 /* Count the number of lines inserted and deleted, and in the case 2029 of continuous wrap mode, how much has changed */ 2030 if (textD->continuousWrap) { 2031 redrawLN = findWrapRange(textD, deletedText, pos, nInserted, nDeleted, 2032 &wrapModStart, &wrapModEnd, &linesInserted, &linesDeleted); 2033 if(!redrawLN && nDeleted > 0) { 2034 for(int i=0;i<nDeleted;i++) { 2035 if(deletedText[i] == '\n') { 2036 redrawLN = 1; 2037 break; 2038 } 2039 } 2040 } 2041 } else { 2042 linesInserted = nInserted == 0 ? 0 : 2043 BufCountLines(buf, pos, pos + nInserted); 2044 linesDeleted = nDeleted == 0 ? 0 : countLines(deletedText); 2045 } 2046 2047 /* Update the line starts and topLineNum */ 2048 if (nInserted != 0 || nDeleted != 0) { 2049 if (textD->continuousWrap) { 2050 updateLineStarts(textD, wrapModStart, wrapModEnd-wrapModStart, 2051 nDeleted + pos-wrapModStart + (wrapModEnd-(pos+nInserted)), 2052 linesInserted, linesDeleted, &scrolled); 2053 } else { 2054 updateLineStarts(textD, pos, nInserted, nDeleted, linesInserted, 2055 linesDeleted, &scrolled); 2056 } 2057 } else 2058 scrolled = False; 2059 2060 /* If we're counting non-wrapped lines as well, maintain the absolute 2061 (non-wrapped) line number of the text displayed */ 2062 if (maintainingAbsTopLineNum(textD) && (nInserted != 0 || nDeleted != 0)) { 2063 if (pos + nDeleted < oldFirstChar) 2064 textD->absTopLineNum += BufCountLines(buf, pos, pos + nInserted) - 2065 countLines(deletedText); 2066 else if (pos < oldFirstChar) 2067 resetAbsLineNum(textD); 2068 } 2069 2070 /* Update the line count for the whole buffer */ 2071 textD->nBufferLines += linesInserted - linesDeleted; 2072 2073 /* Update the scroll bar ranges (and value if the value changed). Note 2074 that updating the horizontal scroll bar range requires scanning the 2075 entire displayed text, however, it doesn't seem to hurt performance 2076 much. Note also, that the horizontal scroll bar update routine is 2077 allowed to re-adjust horizOffset if there is blank space to the right 2078 of all lines of text. */ 2079 updateVScrollBarRange(textD); 2080 scrolled |= updateHScrollBarRange(textD); 2081 2082 /* Update the cursor position */ 2083 if (textD->cursorToHint != NO_HINT) { 2084 textD->cursor->cursorPos = textD->cursorToHint; 2085 textD->cursorToHint = NO_HINT; 2086 } else if (textD->cursor->cursorPos > pos) { 2087 if(textD->mcursorSize > 1) { 2088 // multi cursor update 2089 TextDChangeCursors(textD, pos, nInserted - nDeleted); 2090 } else { 2091 if (textD->cursor->cursorPos < pos + nDeleted) 2092 textD->cursor->cursorPos = pos; 2093 else 2094 textD->cursor->cursorPos += nInserted - nDeleted; 2095 } 2096 } 2097 2098 /* If the changes caused scrolling, re-paint everything and we're done. */ 2099 Bool isLastCursor = textD->cursor == textD->multicursor + textD->mcursorSize - 1; 2100 if(scrolled && isLastCursor) { 2101 blankCursorProtrusions(textD); 2102 TextDRedisplayRect(textD, 0, textD->top, textD->width + textD->left, 2103 textD->height); 2104 if (textD->styleBuffer) {/* See comments in extendRangeForStyleMods */ 2105 textD->styleBuffer->primary.selected = False; 2106 textD->styleBuffer->primary.zeroWidth = False; 2107 } 2108 return; 2109 } 2110 2111 /* If the changes didn't cause scrolling, decide the range of characters 2112 that need to be re-painted. Also if the cursor position moved, be 2113 sure that the redisplay range covers the old cursor position so the 2114 old cursor gets erased, and erase the bits of the cursor which extend 2115 beyond the left and right edges of the text. */ 2116 startDispPos = textD->continuousWrap ? wrapModStart : pos; 2117 int cpos = origCursorPos; 2118 if (textD->highlightCursorLine) { 2119 cpos = BufStartOfLine(buf, origCursorPos); 2120 } 2121 if (origCursorPos == startDispPos && textD->cursor->cursorPos != startDispPos) { 2122 startDispPos = min(startDispPos, BufLeftPos(buf, cpos)); 2123 } 2124 2125 if (linesInserted == linesDeleted) { 2126 if (nInserted == 0 && nDeleted == 0) 2127 endDispPos = pos + nRestyled; 2128 else { 2129 endDispPos = textD->continuousWrap ? wrapModEnd : 2130 BufEndOfLine(buf, pos + nInserted) + 1; 2131 if (origCursorPos >= startDispPos && 2132 (origCursorPos <= endDispPos || endDispPos == buf->length)) 2133 blankCursorProtrusions(textD); 2134 } 2135 /* If more than one line is inserted/deleted, a line break may have 2136 been inserted or removed in between, and the line numbers may 2137 have changed. If only one line is altered, line numbers cannot 2138 be affected (the insertion or removal of a line break always 2139 results in at least two lines being redrawn). */ 2140 // for some reason, a simple insert of one character always results 2141 // in linesInserted > 2 2142 if (linesInserted > 2 || redrawLN) redrawLineNumbers(textD, textD->top, textD->height, True); 2143 } else { /* linesInserted != linesDeleted */ 2144 endDispPos = textD->lastChar + 1; 2145 if (origCursorPos >= pos) { 2146 blankCursorProtrusions(textD); 2147 } 2148 2149 redrawLineNumbers(textD, textD->top, textD->height, True); 2150 } 2151 2152 /* If there is a style buffer, check if the modification caused additional 2153 changes that need to be redisplayed. (Redisplaying separately would 2154 cause double-redraw on almost every modification involving styled 2155 text). Extend the redraw range to incorporate style changes */ 2156 if (textD->styleBuffer) 2157 extendRangeForStyleMods(textD, &startDispPos, &endDispPos); 2158 2159 /* Redisplay computed range */ 2160 textDRedisplayRange(textD, startDispPos, endDispPos); 2161 } 2162 2163 /* 2164 ** In continuous wrap mode, internal line numbers are calculated after 2165 ** wrapping. A separate non-wrapped line count is maintained when line 2166 ** numbering is turned on. There is some performance cost to maintaining this 2167 ** line count, so normally absolute line numbers are not tracked if line 2168 ** numbering is off. This routine allows callers to specify that they still 2169 ** want this line count maintained (for use via TextDPosToLineAndCol). 2170 ** More specifically, this allows the line number reported in the statistics 2171 ** line to be calibrated in absolute lines, rather than post-wrapped lines. 2172 */ 2173 void TextDMaintainAbsLineNum(textDisp *textD, int state) 2174 { 2175 textD->needAbsTopLineNum = state; 2176 resetAbsLineNum(textD); 2177 } 2178 2179 /* 2180 ** Returns the absolute (non-wrapped) line number of the first line displayed. 2181 ** Returns 0 if the absolute top line number is not being maintained. 2182 */ 2183 static int getAbsTopLineNum(textDisp *textD) 2184 { 2185 if (!textD->continuousWrap) 2186 return textD->topLineNum; 2187 if (maintainingAbsTopLineNum(textD)) 2188 return textD->absTopLineNum; 2189 return 0; 2190 } 2191 2192 /* 2193 ** Re-calculate absolute top line number for a change in scroll position. 2194 */ 2195 static void offsetAbsLineNum(textDisp *textD, int oldFirstChar) 2196 { 2197 if (maintainingAbsTopLineNum(textD)) { 2198 if (textD->firstChar < oldFirstChar) 2199 textD->absTopLineNum -= BufCountLines(textD->buffer, 2200 textD->firstChar, oldFirstChar); 2201 else 2202 textD->absTopLineNum += BufCountLines(textD->buffer, 2203 oldFirstChar, textD->firstChar); 2204 } 2205 } 2206 2207 /* 2208 ** Return true if a separate absolute top line number is being maintained 2209 ** (for displaying line numbers or showing in the statistics line). 2210 */ 2211 static int maintainingAbsTopLineNum(textDisp *textD) 2212 { 2213 return textD->continuousWrap && 2214 (textD->lineNumWidth != 0 || textD->needAbsTopLineNum); 2215 } 2216 2217 /* 2218 ** Count lines from the beginning of the buffer to reestablish the 2219 ** absolute (non-wrapped) top line number. If mode is not continuous wrap, 2220 ** or the number is not being maintained, does nothing. 2221 */ 2222 static void resetAbsLineNum(textDisp *textD) 2223 { 2224 textD->absTopLineNum = 1; 2225 offsetAbsLineNum(textD, 0); 2226 } 2227 2228 /* 2229 ** Find the line number of position "pos" relative to the first line of 2230 ** displayed text. Returns False if the line is not displayed. 2231 */ 2232 static int posToVisibleLineNum(textDisp *textD, int pos, int *lineNum) 2233 { 2234 int i; 2235 2236 if (pos < textD->firstChar) 2237 return False; 2238 if (pos > textD->lastChar) { 2239 if (emptyLinesVisible(textD)) { 2240 if (textD->lastChar < textD->buffer->length) { 2241 if (!posToVisibleLineNum(textD, textD->lastChar, lineNum)) { 2242 fprintf(stderr, "xnedit: Consistency check ptvl failed\n"); 2243 return False; 2244 } 2245 return ++(*lineNum) <= textD->nVisibleLines-1; 2246 } else { 2247 posToVisibleLineNum(textD, max(textD->lastChar-1, 0), lineNum); 2248 return True; 2249 } 2250 } 2251 return False; 2252 } 2253 2254 for (i=textD->nVisibleLines-1; i>=0; i--) { 2255 if (textD->lineStarts[i] != -1 && pos >= textD->lineStarts[i]) { 2256 *lineNum = i; 2257 return True; 2258 } 2259 } 2260 2261 return False; 2262 } 2263 2264 /* 2265 * Get the number of bytes for a character 2266 * If ANSI coloring is enabled and the the first character is <ESC> 2267 * return the number of bytes of the escape sequence 2268 */ 2269 static int getCharWidth(textDisp *textD, const char *src_orig, FcChar32 *dst, int len) 2270 { 2271 if(textD->ansiColors && *src_orig == '\e') { 2272 // number of bytes for the complete escape sequence 2273 if(len < 3) return 1; 2274 if(src_orig[1] != '[') return 1; 2275 int i; 2276 for(i=2;i<len;i++) { 2277 char c = src_orig[i]; 2278 if(c == 'm') { 2279 i++; 2280 break; 2281 } 2282 if(c < '0' || (c > '9' && c != ';')) break; 2283 } 2284 *dst = 0; 2285 return i; 2286 } else { 2287 // number of UTF-8 bytes for the Unicode Character 2288 return Utf8ToUcs4(src_orig, dst, len); 2289 } 2290 } 2291 2292 static FcChar32 getCharacter32(const textDisp *textD, const textBuffer* buf, int pos, int *charlen) 2293 { 2294 if(textD->ansiColors) { 2295 if(BufGetCharacter(buf, pos) == '\e') { 2296 if(BufGetCharacter(buf, pos+1) == '[') { 2297 int start = pos; 2298 pos += 2; 2299 char c; 2300 while((c = BufGetCharacter(buf, pos)) != '\0') { 2301 if(c == 'm') { 2302 pos++; 2303 break; 2304 } 2305 if(c < '0' || (c > '9' && c != ';')) break; 2306 pos++; 2307 } 2308 *charlen = pos - start; 2309 return 0; 2310 } 2311 } 2312 } 2313 return BufGetCharacter32(buf, pos, charlen); 2314 } 2315 2316 typedef struct _textCursorX { 2317 int cursorX; 2318 int index; 2319 } textCursorX; 2320 2321 /* 2322 ** Redisplay the text on a single line represented by "visLineNum" (the 2323 ** number of lines down from the top of the display), limited by 2324 ** "leftClip" and "rightClip" window coordinates and "leftCharIndex" and 2325 ** "rightCharIndex" character positions (not including the character at 2326 ** position "rightCharIndex"). 2327 ** 2328 ** The cursor is also drawn if it appears on the line. 2329 */ 2330 static void redisplayLine(textDisp *textD, int visLineNum, int leftClip, 2331 int rightClip, int leftCharIndex, int rightCharIndex) 2332 { 2333 textBuffer *buf = textD->buffer; 2334 int x, y, startX, charIndex, lineStartPos, lineLen, fontHeight, inc; 2335 int stdCharWidth, charWidth, startIndex, charStyle, style; 2336 int charLen, outStartIndex, outIndex, hasCursor = False; 2337 int dispIndexOffset, y_orig; 2338 int startOfLine = INT_MAX; 2339 int endOfLine = 0; 2340 int cursorLine = False; 2341 FcChar32 expandedChar[MAX_EXP_CHAR_LEN]; 2342 FcChar32 outStr[MAX_DISP_LINE_LEN]; 2343 FcChar32 *outPtr; 2344 const char *lineStr; 2345 char *lineStrFree; 2346 char baseChar; 2347 FcChar32 uc = 0; 2348 NFont *styleFL = textD->font; 2349 NFont *charFL; 2350 XftFont *styleFont; 2351 XftFont *charFont; 2352 int rbTabDist = TEXT_OF_TEXTD(textD).emulateTabs > 0 ? 2353 TEXT_OF_TEXTD(textD).emulateTabs : buf->tabDist; 2354 Boolean indentRainbow = textD->indentRainbow; 2355 2356 textCursorX singleCursor = { textD->cursor->cursorPos, 0 }; 2357 textCursorX *cursorX = textD->mcursorSizeReal == 1 ? &singleCursor : NEditCalloc(textD->mcursorSizeReal, sizeof(textCursorX)); 2358 int cursorNum = 0; 2359 int cursorIndex; 2360 2361 /* If line is not displayed, skip it */ 2362 if (visLineNum < 0 || visLineNum >= textD->nVisibleLines) 2363 return; 2364 2365 /* Shrink the clipping range to the active display area */ 2366 leftClip = max(textD->left, leftClip); 2367 rightClip = min(rightClip, textD->left + textD->width); 2368 2369 if (leftClip > rightClip) { 2370 return; 2371 } 2372 2373 /* Calculate y coordinate of the string to draw */ 2374 fontHeight = textD->ascent + textD->descent; 2375 y = textD->top + visLineNum * fontHeight; 2376 2377 /* Get the text, length, and buffer position of the line to display */ 2378 lineStartPos = textD->lineStarts[visLineNum]; 2379 if (lineStartPos == -1) { 2380 lineLen = 0; 2381 lineStr = NULL; 2382 lineStrFree = NULL; 2383 } else { 2384 lineLen = visLineLength(textD, visLineNum); 2385 lineStr = BufGetRange2(buf, lineStartPos, lineStartPos + lineLen, &lineStrFree); 2386 endOfLine = BufEndOfLine(buf, lineStartPos); 2387 if(textD->highlightCursorLine) { 2388 startOfLine = BufStartOfLine(buf, lineStartPos); 2389 } 2390 } 2391 2392 /* Get the active ANSI color/style */ 2393 int ansiS = -1; 2394 int ansiCharS = -1; 2395 ansiStyle newAnsiStyle = {-1, -1, -1, -1}; 2396 ansiStyle ansi = {-1, -1, -1, -1}; 2397 if(textD->ansiColors && lineStartPos >= 0) { 2398 findActiveAnsiStyle(textD, lineStartPos, &ansi); 2399 } 2400 2401 /* Space beyond the end of the line is still counted in units of characters 2402 of a standardized character width (this is done mostly because style 2403 changes based on character position can still occur in this region due 2404 to rectangular selections). stdCharWidth must be non-zero to prevent a 2405 potential infinite loop if x does not advance */ 2406 stdCharWidth = textD->font->maxWidth; 2407 if (stdCharWidth <= 0) { 2408 fprintf(stderr, "xnedit: Internal Error, bad font measurement\n"); 2409 NEditFree(lineStrFree); 2410 return; 2411 } 2412 2413 /* Rectangular selections are based on "real" line starts (after a newline 2414 or start of buffer). Calculate the difference between the last newline 2415 position and the line start we're using. Since scanning back to find a 2416 newline is expensive, only do so if there's actually a rectangular 2417 selection which needs it */ 2418 if (textD->continuousWrap && (rangeTouchesRectSel(&buf->primary, 2419 lineStartPos, lineStartPos + lineLen) || rangeTouchesRectSel( 2420 &buf->secondary, lineStartPos, lineStartPos + lineLen) || 2421 rangeTouchesRectSel(&buf->highlight, lineStartPos, 2422 lineStartPos + lineLen))) { 2423 dispIndexOffset = BufCountDispChars(buf, 2424 BufStartOfLine(buf, lineStartPos), lineStartPos); 2425 } else 2426 dispIndexOffset = 0; 2427 2428 /* Step through character positions from the beginning of the line (even if 2429 that's off the left edge of the displayed area) to find the first 2430 character position that's not clipped, and the x coordinate for drawing 2431 that character */ 2432 x = textD->left - textD->horizOffset; 2433 outIndex = 0; 2434 2435 int rbEnd = lineLen; 2436 int rbCharIndex = 0; 2437 int rbPixelIndex = 0; 2438 2439 inc = 1; 2440 for (charIndex = 0; ; charIndex+=inc) { 2441 if(charIndex >= lineLen) { 2442 baseChar = '\0'; 2443 charLen = 1; 2444 inc = 1; 2445 } else { 2446 baseChar = lineStr[charIndex]; 2447 const char *line = lineStr + charIndex; 2448 int remainingLen = lineLen - charIndex; 2449 2450 inc = getCharWidth(textD, line, &uc, remainingLen); 2451 if(inc > 1) { 2452 charLen = 1; 2453 expandedChar[0] = uc; 2454 } else { 2455 charLen = BufExpandCharacter4(lineStr[charIndex], 2456 outIndex, 2457 expandedChar, 2458 buf->tabDist, buf->nullSubsChar); 2459 } 2460 } 2461 2462 style = styleOfPos(textD, lineStartPos, lineLen, charIndex, 2463 outIndex + dispIndexOffset, baseChar); 2464 charWidth = charIndex >= lineLen 2465 ? stdCharWidth 2466 : charWidth4( 2467 textD, 2468 expandedChar, 2469 charLen, 2470 styleFL = styleFontList(textD, style)); 2471 2472 if (x + charWidth >= leftClip && charIndex >= leftCharIndex) { 2473 startIndex = charIndex; 2474 outStartIndex = outIndex; 2475 startX = x; 2476 styleFont = FindFont(styleFL, uc); 2477 break; 2478 } 2479 2480 if(textD->ansiColors && baseChar == '\e') { 2481 newAnsiStyle.fg = -1; 2482 newAnsiStyle.bg = -1; 2483 newAnsiStyle.bold = -1; 2484 newAnsiStyle.italic = -1; 2485 parseEscapeSequence(buf, lineStartPos + charIndex, &newAnsiStyle); 2486 extendAnsiStyle(&ansi, &newAnsiStyle); 2487 ansiS = charIndex; 2488 } 2489 2490 if(indentRainbow) { 2491 if(isspace(baseChar)) { 2492 if(baseChar == '\t') { 2493 rbCharIndex += buf->tabDist - rbCharIndex % buf->tabDist; 2494 } else { 2495 rbCharIndex++; 2496 } 2497 } else { 2498 rbEnd = charIndex; 2499 } 2500 } 2501 2502 x += charWidth; 2503 outIndex += charLen; 2504 } 2505 2506 rbPixelIndex = rbCharIndex / rbTabDist; 2507 if(rbEnd < startIndex) { 2508 indentRainbow = 0; 2509 } 2510 2511 2512 /* Set Xrender clipping to prevent text rendering beyond the line borders. 2513 * Sometimes with anti aliasing transparent pixels are above the glyph 2514 */ 2515 if(textD->d) { 2516 XRectangle rect; 2517 rect.x = 0; 2518 rect.y = 0; 2519 rect.width = rightClip - leftClip; 2520 rect.height = textD->ascent + textD->descent; 2521 XftDrawSetClipRectangles(textD->d, leftClip, y, &rect, 1); 2522 } 2523 2524 int rbCurrentPixelIndex = 0; 2525 if(!indentRainbow) { 2526 rbCurrentPixelIndex = -1; 2527 rbPixelIndex = -1; 2528 } 2529 2530 /* check if the line contains the cursor 2531 */ 2532 if(textD->highlightCursorLine && TextDRangeHasCursor(textD, startOfLine, endOfLine)) { 2533 cursorLine = True; 2534 } 2535 2536 /* Scan character positions from the beginning of the clipping range, and 2537 draw parts whenever the style changes (also note if the cursor is on 2538 this line, and where it should be drawn to take advantage of the x 2539 position which we've gone to so much trouble to calculate) */ 2540 outPtr = outStr; 2541 outIndex = outStartIndex; 2542 x = startX; 2543 inc = 1; 2544 for (charIndex = startIndex; charIndex < rightCharIndex; charIndex += inc) { 2545 if (TextDPosHasCursor(textD, lineStartPos+charIndex, &cursorIndex)) { 2546 if (charIndex < lineLen 2547 || (charIndex == lineLen && (lineStartPos+charIndex) >= buf->length)) { 2548 hasCursor = True; 2549 cursorX[cursorNum].cursorX = x - 1; 2550 cursorX[cursorNum].index = cursorIndex; 2551 cursorNum++; 2552 } else if (charIndex == lineLen) { 2553 if (wrapUsesCharacter(textD, (lineStartPos+charIndex))) { 2554 hasCursor = True; 2555 cursorX[cursorNum].cursorX = x - 1; 2556 cursorX[cursorNum].index = cursorIndex; 2557 cursorNum++; 2558 } 2559 } 2560 } 2561 2562 int cpCharLen; 2563 if(charIndex >= lineLen) { 2564 baseChar = '\0'; 2565 charLen = 1; 2566 rbCurrentPixelIndex = -1; 2567 cpCharLen = -1; 2568 } else { 2569 baseChar = lineStr[charIndex]; 2570 2571 if(indentRainbow) { 2572 if(isspace(baseChar)) { 2573 if(baseChar == '\t') { 2574 rbCharIndex += buf->tabDist - rbCharIndex % buf->tabDist; 2575 } else { 2576 rbCharIndex++; 2577 } 2578 rbCurrentPixelIndex = ((rbCharIndex+rbTabDist-1) / rbTabDist)+textD->colorProfile->numRainbowColors -1; 2579 } else { 2580 rbCurrentPixelIndex = -1; 2581 indentRainbow = 0; 2582 if(rbCharIndex % rbTabDist > 0) { 2583 // don't highlight partial indention 2584 rbPixelIndex = -2; // set to negative value but not -1 2585 } 2586 } 2587 } 2588 2589 inc = getCharWidth(textD, lineStr+charIndex, &uc, lineLen - charIndex); 2590 if(inc > 1) { 2591 if(uc != 0) { 2592 charLen = 1; 2593 expandedChar[0] = uc; 2594 } else { 2595 charLen = 0; 2596 } 2597 } else { 2598 charLen = BufExpandCharacter4(lineStr[charIndex], 2599 outIndex, 2600 expandedChar, 2601 buf->tabDist, buf->nullSubsChar); 2602 } 2603 cpCharLen = charLen; 2604 } 2605 2606 if(textD->ansiColors && baseChar == '\e') { 2607 newAnsiStyle.fg = -1; 2608 newAnsiStyle.bg = -1; 2609 newAnsiStyle.bold = -1; 2610 newAnsiStyle.italic = -1; 2611 parseEscapeSequence(buf, lineStartPos + charIndex, &newAnsiStyle); 2612 ansiCharS = charIndex; 2613 } 2614 2615 charStyle = styleOfPos(textD, lineStartPos, lineLen, charIndex, 2616 outIndex + dispIndexOffset, baseChar); 2617 charFL = styleFontList(textD, charStyle); 2618 charFont = FindFont(charFL, uc); 2619 2620 if (charStyle != style || charFont != styleFont || rbPixelIndex != rbCurrentPixelIndex || ansiS != ansiCharS) { 2621 drawString(textD, style, rbPixelIndex, startX, y, max(startX, leftClip), min(x, rightClip), outStr, outPtr - outStr, cursorLine, &ansi); 2622 outPtr = outStr; 2623 startX = x; 2624 style = charStyle; 2625 styleFont = charFont; 2626 rbPixelIndex = rbCurrentPixelIndex; 2627 extendAnsiStyle(&ansi, &newAnsiStyle); 2628 } 2629 ansiS = ansiCharS; 2630 2631 if(cpCharLen == 1) { 2632 *outPtr = *expandedChar; 2633 charWidth = charWidth4(textD, expandedChar, charLen, charFL); 2634 } else if(charIndex < lineLen) { 2635 memcpy(outPtr, expandedChar, sizeof(FcChar32)*charLen); 2636 charWidth = charWidth4(textD, expandedChar, charLen, charFL); 2637 } else { 2638 charWidth = stdCharWidth; 2639 } 2640 2641 outPtr += charLen; 2642 x += charWidth; 2643 outIndex += charLen; 2644 2645 if (outPtr - outStr + MAX_EXP_CHAR_LEN >= MAX_DISP_LINE_LEN 2646 || x >= rightClip) { 2647 break; 2648 } 2649 } 2650 extendAnsiStyle(&ansi, &newAnsiStyle); 2651 2652 /* Draw the remaining style segment */ 2653 drawString(textD, style, rbCurrentPixelIndex, startX, y, max(startX, leftClip), min(x, rightClip), outStr, outPtr - outStr, cursorLine, &ansi); 2654 2655 /* Draw the cursor if part of it appeared on the redisplayed part of 2656 this line. Also check for the cases which are not caught as the 2657 line is scanned above: when the cursor appears at the very end 2658 of the redisplayed section. */ 2659 y_orig = textD->cursor->y; 2660 if (textD->cursorOn) { 2661 if (hasCursor) { 2662 for(int i=0;i<cursorNum;i++) { 2663 drawCursor(textD, cursorX[i].cursorX, y); 2664 cursorIndex = cursorX[i].index; 2665 if(cursorIndex >= 0) { 2666 textD->multicursor[cursorIndex].x = cursorX[i].cursorX; 2667 textD->multicursor[cursorIndex].y = y; 2668 } 2669 } 2670 } else if (charIndex < lineLen 2671 && TextDPosHasCursor(textD, lineStartPos+charIndex+1, &cursorIndex) 2672 && x == rightClip) { 2673 if ((lineStartPos+charIndex+1) >= buf->length) { 2674 drawCursor(textD, x - 1, y); 2675 if(cursorIndex >= 0) { 2676 textD->multicursor[cursorIndex].x = x - 1; 2677 textD->multicursor[cursorIndex].y = y; 2678 } 2679 } else { 2680 if (wrapUsesCharacter(textD, lineStartPos+charIndex+1)) { 2681 drawCursor(textD, x - 1, y); 2682 if(cursorIndex >= 0) { 2683 textD->multicursor[cursorIndex].x = x - 1; 2684 textD->multicursor[cursorIndex].y = y; 2685 } 2686 } 2687 } 2688 } else if (TextDPosHasCursor(textD, lineStartPos + rightCharIndex, &cursorIndex)) { 2689 drawCursor(textD, x - 1, y); 2690 if(cursorIndex >= 0) { 2691 textD->multicursor[cursorIndex].x = x - 1; 2692 textD->multicursor[cursorIndex].y = y; 2693 } 2694 } 2695 } 2696 2697 // set the position where the input method might pop up 2698 if(hasCursor && textD->mcursorSizeReal == 1) { 2699 int cx = cursorX[0].cursorX; 2700 // xic position should the the cursor 2701 if(cx != textD->xic_x || y != textD->xic_y) { 2702 TextWidget textwidget = (TextWidget)textD->w; 2703 if(textwidget->text.xic) { 2704 // set x to the left edge of the cursor 2705 XPoint ipos; 2706 ipos.x = cx - (FontDefault(textD->font)->max_advance_width / 3); 2707 ipos.y = y; 2708 XVaNestedList xicvals; 2709 xicvals = XVaCreateNestedList(0, XNSpotLocation, &ipos, NULL); 2710 XSetICValues(textwidget->text.xic, XNPreeditAttributes, xicvals, NULL); 2711 XFree(xicvals); 2712 textD->xic_x = cx; 2713 textD->xic_y = y; 2714 } 2715 } 2716 } 2717 2718 /* If the y position of the cursor has changed, redraw the calltip */ 2719 if (hasCursor && (y_orig != textD->cursor->y || y_orig != y)) 2720 TextDRedrawCalltip(textD, 0); 2721 2722 NEditFree(lineStrFree); 2723 if(textD->mcursorSizeReal > 1) NEditFree(cursorX); 2724 } 2725 2726 /* 2727 ** Draw a string or blank area according to parameter "style", using the 2728 ** appropriate colors and drawing method for that style, with top left 2729 ** corner at x, y. If style says to draw text, use "string" as source of 2730 ** characters, and draw "nChars", if style is FILL, erase 2731 ** rectangle where text would have drawn from x to toX and from y to 2732 ** the maximum y extent of the current font(s). 2733 */ 2734 static void drawString(textDisp *textD, int style, int rbIndex, int x, int y, int fromX, 2735 int toX, FcChar32 *string, int nChars, Boolean highlightLine, ansiStyle *ansi) 2736 { 2737 if(toX < fromX || nChars == 0) return; 2738 2739 XftColor *gc = &textD->colorProfile->textFgColor; 2740 NFont *fontList = textD->font; 2741 XftColor *bground = &textD->colorProfile->textBgColor; 2742 XftColor *fground = &textD->colorProfile->textFgColor; 2743 int underlineStyle = FALSE; 2744 XftColor color = textD->colorProfile->textFgColor; 2745 2746 /* Don't draw if widget isn't realized */ 2747 if (XtWindow(textD->w) == 0) 2748 return; 2749 2750 if(nChars == 0) rbIndex = -1; 2751 2752 /* select a GC */ 2753 if (rbIndex >= 0 || style & (STYLE_LOOKUP_MASK | BACKLIGHT_MASK | RANGESET_MASK)) { 2754 gc = &textD->styleGC; 2755 } 2756 else if (style & HIGHLIGHT_MASK) { 2757 bground = &textD->colorProfile->hiliteBgColor; 2758 color = textD->colorProfile->hiliteFgColor; 2759 } 2760 else if (style & PRIMARY_MASK) { 2761 bground = &textD->colorProfile->selectBgColor; 2762 color = textD->colorProfile->selectFgColor; 2763 } 2764 else if (highlightLine && textD->highlightCursorLine) { 2765 bground = &textD->colorProfile->lineHiBgColor; 2766 } 2767 else { 2768 gc = &textD->colorProfile->textFgColor; 2769 } 2770 2771 if (gc == &textD->styleGC) { 2772 /* we have work to do */ 2773 styleTableEntry *styleRec; 2774 /* Set font, color, and gc depending on style. For normal text, GCs 2775 for normal drawing, or drawing within a selection or highlight are 2776 pre-allocated and pre-configured. For syntax highlighting, GCs are 2777 configured here, on the fly. */ 2778 if (style & STYLE_LOOKUP_MASK) { 2779 styleRec = &textD->styleTable[(style & STYLE_LOOKUP_MASK) - ASCII_A]; 2780 underlineStyle = styleRec->underline; 2781 fontList = styleRec->font; 2782 //fground = styleRec->color; 2783 color = styleRec->color; 2784 /* here you could pick up specific select and highlight fground */ 2785 } 2786 else { 2787 styleRec = NULL; 2788 fground = &textD->colorProfile->textFgColor; 2789 } 2790 /* Background color priority order is: 2791 1 Primary(Selection), 2 Highlight(Parens), 2792 3 Rangeset, 4 SyntaxHighlightStyle, 2793 5 Backlight (if NOT fill), 6 DefaultBackground */ 2794 bground = 2795 style & PRIMARY_MASK ? &textD->colorProfile->selectBgColor : 2796 style & HIGHLIGHT_MASK ? &textD->colorProfile->hiliteBgColor : 2797 style & RANGESET_MASK ? 2798 getRangesetColor(textD, 2799 (style&RANGESET_MASK)>>RANGESET_SHIFT, 2800 bground) : 2801 rbIndex >= 0 ? &textD->colorProfile->rainbowColors[rbIndex%textD->colorProfile->numRainbowColors] : 2802 styleRec && styleRec->bgColorName ? &styleRec->bgColor : 2803 (style & BACKLIGHT_MASK) && !(style & FILL_MASK) ? 2804 &textD->bgClassPixel[(style>>BACKLIGHT_SHIFT) & 0xff] : 2805 &textD->colorProfile->textBgColor; 2806 if (fground == bground) /* B&W kludge */ 2807 fground = &textD->colorProfile->textBgColor; 2808 /* set up gc for clearing using the foreground color entry */ 2809 2810 if((bground == &textD->colorProfile->textBgColor || rbIndex >= 0) && highlightLine && textD->highlightCursorLine) { 2811 bground = &textD->colorProfile->lineHiBgColor; 2812 } 2813 } 2814 2815 /* Set ANSI color */ 2816 XftColor ansiBGColor; 2817 if(ansi->fg > 0) { 2818 ansiFgToColorIndex(textD, ansi->fg, &color); 2819 } 2820 if(ansi->bg > 0 && bground == &textD->colorProfile->textBgColor) { 2821 ansiBgToColorIndex(textD, ansi->bg, &ansiBGColor); 2822 bground = &ansiBGColor; 2823 } 2824 if(ansi->bold > 0 || ansi->italic > 0) { 2825 if(ansi->bold == ansi->italic) { 2826 fontList = textD->boldItalicFont; 2827 } else if(ansi->bold == 1) { 2828 fontList = textD->boldFont; 2829 } else { 2830 fontList = textD->italicFont; 2831 } 2832 } 2833 2834 2835 /* Always draw blank area, because Xft AA text rendering needs a clean 2836 * background */ 2837 2838 /* wipes out to right hand edge of widget */ 2839 if (toX >= textD->left) { 2840 clearRect( 2841 textD, 2842 bground, 2843 fromX, 2844 y, 2845 toX - fromX, 2846 textD->ascent + textD->descent); 2847 } 2848 if(style & FILL_MASK) { 2849 return; 2850 } 2851 2852 2853 /* We assume the string should be rendered with just one font, because 2854 * redisplayLine breaks the strings when a different font is required. 2855 * The first character in the string determines the charset and FindFont 2856 * returns a Font for this. 2857 */ 2858 XftFont *font = FindFont(fontList, string[0]); 2859 2860 /* If any space around the character remains unfilled (due to use of 2861 different sized fonts for highlighting), fill in above or below 2862 to erase previously drawn characters */ 2863 if (font->ascent < textD->ascent) 2864 clearRect(textD, bground, fromX, y, toX - fromX, textD->ascent - font->ascent); 2865 if (font->descent < textD->descent) 2866 clearRect(textD, bground, fromX, y + textD->ascent + font->descent, toX - fromX, 2867 textD->descent - font->descent); 2868 2869 2870 2871 2872 /* Draw the string using color and font set above */ 2873 XftDrawString32(textD->d, &color, font, x, y + textD->ascent, string, nChars); 2874 2875 /* Underline if style is secondary selection */ 2876 if (style & SECONDARY_MASK || underlineStyle) 2877 { 2878 /* draw underline */ 2879 //XDrawLine(XtDisplay(textD->w), XtWindow(textD->w), gc, x, 2880 // y + textD->ascent, toX - 1, y + textD->ascent); 2881 XftDrawRect(textD->d, fground, x, y + textD->ascent, toX - 1, 1); 2882 } 2883 } 2884 2885 /* 2886 ** Clear a rectangle with the appropriate background color for "style" 2887 */ 2888 static void clearRect(textDisp *textD, XftColor *color, int x, int y, 2889 int width, int height) 2890 { 2891 /* A width of zero means "clear to end of window" to XClearArea */ 2892 if (width == 0 || XtWindow(textD->w) == 0) 2893 return; 2894 2895 if (color == &textD->colorProfile->textBgColor) { 2896 XClearArea(XtDisplay(textD->w), XtWindow(textD->w), x, y, 2897 width, height, False); 2898 } 2899 else { 2900 XftDrawRect(textD->d, color, x, y, width, height); 2901 } 2902 } 2903 2904 /* 2905 ** Draw a cursor with top center at x, y. 2906 */ 2907 static void drawCursor(textDisp *textD, int x, int y) 2908 { 2909 XSegment segs[5]; 2910 int left, right, cursorWidth, midY; 2911 int fontWidth = textD->font->minWidth; 2912 int fontHeight = textD->ascent + textD->descent; 2913 int nSegs = 0; 2914 int bot = y + fontHeight - 1; 2915 2916 if (XtWindow(textD->w) == 0 || x < textD->left-1 || 2917 x > textD->left + textD->width) 2918 return; 2919 2920 /* For cursors other than the block, make them around 2/3 of a character 2921 width, rounded to an even number of pixels so that X will draw an 2922 odd number centered on the stem at x. */ 2923 cursorWidth = (fontWidth/3) * 2; 2924 2925 // simplify the cursor in multicursor mode 2926 if(textD->mcursorOn && textD->cursorStyle != CARET_CURSOR && textD->cursorStyle != BLOCK_CURSOR) { 2927 cursorWidth = 0; 2928 } 2929 2930 left = x - cursorWidth/2; 2931 right = left + cursorWidth; 2932 2933 /* Create segments and draw cursor */ 2934 if (textD->cursorStyle == CARET_CURSOR) { 2935 midY = bot - fontHeight/5; 2936 segs[0].x1 = left; segs[0].y1 = bot; segs[0].x2 = x; segs[0].y2 = midY; 2937 segs[1].x1 = x; segs[1].y1 = midY; segs[1].x2 = right; segs[1].y2 = bot; 2938 segs[2].x1 = left; segs[2].y1 = bot; segs[2].x2 = x; segs[2].y2=midY-1; 2939 segs[3].x1 = x; segs[3].y1=midY-1; segs[3].x2 = right; segs[3].y2 = bot; 2940 nSegs = 4; 2941 } else if (textD->cursorStyle == NORMAL_CURSOR) { 2942 segs[0].x1 = left; segs[0].y1 = y; segs[0].x2 = right; segs[0].y2 = y; 2943 segs[1].x1 = x; segs[1].y1 = y; segs[1].x2 = x; segs[1].y2 = bot; 2944 segs[2].x1 = left; segs[2].y1 = bot; segs[2].x2 = right; segs[2].y2=bot; 2945 nSegs = 3; 2946 } else if (textD->cursorStyle == HEAVY_CURSOR) { 2947 segs[0].x1 = x-1; segs[0].y1 = y; segs[0].x2 = x-1; segs[0].y2 = bot; 2948 segs[1].x1 = x; segs[1].y1 = y; segs[1].x2 = x; segs[1].y2 = bot; 2949 segs[2].x1 = x+1; segs[2].y1 = y; segs[2].x2 = x+1; segs[2].y2 = bot; 2950 segs[3].x1 = left; segs[3].y1 = y; segs[3].x2 = right; segs[3].y2 = y; 2951 segs[4].x1 = left; segs[4].y1 = bot; segs[4].x2 = right; segs[4].y2=bot; 2952 nSegs = 5; 2953 } else if (textD->cursorStyle == DIM_CURSOR) { 2954 midY = y + fontHeight/2; 2955 segs[0].x1 = x; segs[0].y1 = y; segs[0].x2 = x; segs[0].y2 = y; 2956 segs[1].x1 = x; segs[1].y1 = midY; segs[1].x2 = x; segs[1].y2 = midY; 2957 segs[2].x1 = x; segs[2].y1 = bot; segs[2].x2 = x; segs[2].y2 = bot; 2958 nSegs = 3; 2959 } else if (textD->cursorStyle == BLOCK_CURSOR) { 2960 right = x + fontWidth; 2961 segs[0].x1 = x; segs[0].y1 = y; segs[0].x2 = right; segs[0].y2 = y; 2962 segs[1].x1 = right; segs[1].y1 = y; segs[1].x2 = right; segs[1].y2=bot; 2963 segs[2].x1 = right; segs[2].y1 = bot; segs[2].x2 = x; segs[2].y2 = bot; 2964 segs[3].x1 = x; segs[3].y1 = bot; segs[3].x2 = x; segs[3].y2 = y; 2965 nSegs = 4; 2966 } 2967 XDrawSegments(XtDisplay(textD->w), XtWindow(textD->w), 2968 textD->cursorFGGC, segs, nSegs); 2969 2970 /* Save the last position drawn */ 2971 textD->cursor->x = x; 2972 textD->cursor->y = y; 2973 } 2974 2975 /* 2976 ** Determine the drawing method to use to draw a specific character from "buf". 2977 ** "lineStartPos" gives the character index where the line begins, "lineIndex", 2978 ** the number of characters past the beginning of the line, and "dispIndex", 2979 ** the number of displayed characters past the beginning of the line. Passing 2980 ** lineStartPos of -1 returns the drawing style for "no text". 2981 ** 2982 ** Why not just: styleOfPos(textD, pos)? Because style applies to blank areas 2983 ** of the window beyond the text boundaries, and because this routine must also 2984 ** decide whether a position is inside of a rectangular selection, and do so 2985 ** efficiently, without re-counting character positions from the start of the 2986 ** line. 2987 ** 2988 ** Note that style is a somewhat incorrect name, drawing method would 2989 ** be more appropriate. 2990 */ 2991 static int styleOfPos(textDisp *textD, int lineStartPos, 2992 int lineLen, int lineIndex, int dispIndex, int thisChar) 2993 { 2994 textBuffer *buf = textD->buffer; 2995 textBuffer *styleBuf = textD->styleBuffer; 2996 int pos, style = 0; 2997 2998 if (lineStartPos == -1 || buf == NULL) 2999 return FILL_MASK; 3000 3001 pos = lineStartPos + min(lineIndex, lineLen); 3002 3003 if (lineIndex >= lineLen) 3004 style = FILL_MASK; 3005 else if (styleBuf != NULL) { 3006 style = (unsigned char)BufGetCharacter(styleBuf, pos); 3007 if (style == textD->unfinishedStyle) { 3008 /* encountered "unfinished" style, trigger parsing */ 3009 (textD->unfinishedHighlightCB)(textD, pos, textD->highlightCBArg); 3010 style = (unsigned char)BufGetCharacter(styleBuf, pos); 3011 } 3012 } 3013 if (inSelection(&buf->primary, pos, lineStartPos, dispIndex)) 3014 style |= PRIMARY_MASK; 3015 if (inSelection(&buf->highlight, pos, lineStartPos, dispIndex)) 3016 style |= HIGHLIGHT_MASK; 3017 if (inSelection(&buf->secondary, pos, lineStartPos, dispIndex)) 3018 style |= SECONDARY_MASK; 3019 /* store in the RANGESET_MASK portion of style the rangeset index for pos */ 3020 if (buf->rangesetTable) { 3021 int rangesetIndex = RangesetIndex1ofPos(buf->rangesetTable, pos, True); 3022 style |= ((rangesetIndex << RANGESET_SHIFT) & RANGESET_MASK); 3023 } 3024 /* store in the BACKLIGHT_MASK portion of style the background color class 3025 of the character thisChar */ 3026 if (textD->bgClass) 3027 { 3028 style |= (textD->bgClass[(unsigned char)thisChar]<<BACKLIGHT_SHIFT); 3029 } 3030 return style; 3031 } 3032 3033 3034 /* 3035 ** Find the width of a string in the font of a particular style 3036 */ 3037 static int charWidth4(const textDisp* textD, const FcChar32* string, 3038 int length, NFont *fontList) 3039 { 3040 if(string[0] == 0) return 0; // special case for ansi coloring 3041 3042 int strWidth = 0; 3043 if(fontList->minWidth == fontList->maxWidth && string[0] < 128) { 3044 strWidth += fontList->minWidth * length; 3045 } else { 3046 // main font is not a monospace font or character is not ascii 3047 XftFont *font = FindFont(fontList, string[0]); 3048 XGlyphInfo extents; 3049 XftTextExtents32(XtDisplay(textD->w), font, string, length, &extents); 3050 strWidth = extents.xOff; 3051 } 3052 3053 return strWidth; 3054 } 3055 3056 static NFont* styleFontList(const textDisp* textD, int style) 3057 { 3058 NFont *font; 3059 if (style & STYLE_LOOKUP_MASK) 3060 font = textD->styleTable[(style & STYLE_LOOKUP_MASK) - ASCII_A].font; 3061 else 3062 font = textD->font; 3063 return font; 3064 } 3065 3066 /* 3067 ** Return true if position "pos" with indentation "dispIndex" is in 3068 ** selection "sel" 3069 */ 3070 static int inSelection(selection *sel, int pos, int lineStartPos, int dispIndex) 3071 { 3072 return sel->selected && 3073 ((!sel->rectangular && 3074 pos >= sel->start && pos < sel->end) || 3075 (sel->rectangular && 3076 pos >= sel->start && lineStartPos <= sel->end && 3077 dispIndex >= sel->rectStart && dispIndex < sel->rectEnd)); 3078 } 3079 3080 /* 3081 ** Translate window coordinates to the nearest (insert cursor or character 3082 ** cell) text position. The parameter posType specifies how to interpret the 3083 ** position: CURSOR_POS means translate the coordinates to the nearest cursor 3084 ** position, and CHARACTER_POS means return the position of the character 3085 ** closest to (x, y). 3086 */ 3087 static int xyToPos(textDisp *textD, int x, int y, int posType) 3088 { 3089 int charIndex, lineStart, lineLen, fontHeight; 3090 int charWidth, charLen, charStyle, visLineNum, xStep, outIndex, inc; 3091 FcChar32 expandedChar[MAX_EXP_CHAR_LEN]; 3092 FcChar32 uc = 0; 3093 NFont *font; 3094 3095 /* Find the visible line number corresponding to the y coordinate */ 3096 fontHeight = textD->ascent + textD->descent; 3097 visLineNum = (y - textD->top) / fontHeight; 3098 if (visLineNum < 0) 3099 return textD->firstChar; 3100 if (visLineNum >= textD->nVisibleLines) 3101 visLineNum = textD->nVisibleLines - 1; 3102 3103 /* Find the position at the start of the line */ 3104 lineStart = textD->lineStarts[visLineNum]; 3105 3106 /* If the line start was empty, return the last position in the buffer */ 3107 if (lineStart == -1) 3108 return textD->buffer->length; 3109 3110 /* Get the line text and its length */ 3111 lineLen = visLineLength(textD, visLineNum); 3112 char *lineStrAlloc; 3113 const char *lineStr = BufGetRange2(textD->buffer, lineStart, lineStart + lineLen, &lineStrAlloc); 3114 3115 /* Step through character positions from the beginning of the line 3116 to find the character position corresponding to the x coordinate */ 3117 xStep = textD->left - textD->horizOffset; 3118 outIndex = 0; 3119 inc = 1; 3120 for(charIndex=0; charIndex<lineLen; charIndex+=inc) { 3121 inc = getCharWidth(textD, lineStr+charIndex, &uc, lineLen-charIndex); 3122 if(inc > 1) { 3123 /* not ascii */ 3124 charLen = 1; 3125 expandedChar[0] = uc; 3126 } else { 3127 charLen = BufExpandCharacter4(lineStr[charIndex], 3128 outIndex, 3129 expandedChar, 3130 textD->buffer->tabDist, textD->buffer->nullSubsChar); 3131 } 3132 3133 charStyle = styleOfPos(textD, lineStart, lineLen, charIndex, outIndex, 3134 lineStr[charIndex]); 3135 font = styleFontList(textD, charStyle); 3136 charWidth = charWidth4(textD, expandedChar, charLen, font); 3137 if (x < xStep + (posType == CURSOR_POS ? charWidth/2 : charWidth)) { 3138 NEditFree(lineStrAlloc); 3139 return lineStart + charIndex; 3140 } 3141 xStep += charWidth; 3142 outIndex += charLen; 3143 } 3144 3145 /* If the x position was beyond the end of the line, return the position 3146 of the newline at the end of the line */ 3147 NEditFree(lineStrAlloc); 3148 return lineStart + lineLen; 3149 } 3150 3151 /* 3152 ** Translate window coordinates to the nearest row and column number for 3153 ** positioning the cursor. This, of course, makes no sense when the font is 3154 ** proportional, since there are no absolute columns. The parameter posType 3155 ** specifies how to interpret the position: CURSOR_POS means translate the 3156 ** coordinates to the nearest position between characters, and CHARACTER_POS 3157 ** means translate the position to the nearest character cell. 3158 */ 3159 static void xyToUnconstrainedPos(textDisp *textD, int x, int y, int *row, 3160 int *column, int posType) 3161 { 3162 int fontHeight = textD->ascent + textD->descent; 3163 int fontWidth = textD->font->maxWidth; 3164 3165 /* Find the visible line number corresponding to the y coordinate */ 3166 *row = (y - textD->top) / fontHeight; 3167 if (*row < 0) *row = 0; 3168 if (*row >= textD->nVisibleLines) *row = textD->nVisibleLines - 1; 3169 *column = ((x-textD->left) + textD->horizOffset + 3170 (posType == CURSOR_POS ? fontWidth/2 : 0)) / fontWidth; 3171 if (*column < 0) *column = 0; 3172 } 3173 3174 /* 3175 ** Offset the line starts array, topLineNum, firstChar and lastChar, for a new 3176 ** vertical scroll position given by newTopLineNum. If any currently displayed 3177 ** lines will still be visible, salvage the line starts values, otherwise, 3178 ** count lines from the nearest known line start (start or end of buffer, or 3179 ** the closest value in the lineStarts array) 3180 */ 3181 static void offsetLineStarts(textDisp *textD, int newTopLineNum) 3182 { 3183 int oldTopLineNum = textD->topLineNum; 3184 int oldFirstChar = textD->firstChar; 3185 int lineDelta = newTopLineNum - oldTopLineNum; 3186 int nVisLines = textD->nVisibleLines; 3187 int *lineStarts = textD->lineStarts; 3188 int i, lastLineNum; 3189 textBuffer *buf = textD->buffer; 3190 3191 /* If there was no offset, nothing needs to be changed */ 3192 if (lineDelta == 0) 3193 return; 3194 3195 /* { int i; 3196 printf("Scroll, lineDelta %d\n", lineDelta); 3197 printf("lineStarts Before: "); 3198 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]); 3199 printf("\n"); 3200 } */ 3201 3202 /* Find the new value for firstChar by counting lines from the nearest 3203 known line start (start or end of buffer, or the closest value in the 3204 lineStarts array) */ 3205 lastLineNum = oldTopLineNum + nVisLines - 1; 3206 if (newTopLineNum < oldTopLineNum && newTopLineNum < -lineDelta) { 3207 textD->firstChar = TextDCountForwardNLines(textD, 0, newTopLineNum-1, 3208 True); 3209 /* printf("counting forward %d lines from start\n", newTopLineNum-1);*/ 3210 } else if (newTopLineNum < oldTopLineNum) { 3211 textD->firstChar = TextDCountBackwardNLines(textD, textD->firstChar, 3212 -lineDelta); 3213 /* printf("counting backward %d lines from firstChar\n", -lineDelta);*/ 3214 } else if (newTopLineNum < lastLineNum) { 3215 textD->firstChar = lineStarts[newTopLineNum - oldTopLineNum]; 3216 /* printf("taking new start from lineStarts[%d]\n", 3217 newTopLineNum - oldTopLineNum); */ 3218 } else if (newTopLineNum-lastLineNum < textD->nBufferLines-newTopLineNum) { 3219 textD->firstChar = TextDCountForwardNLines(textD, 3220 lineStarts[nVisLines-1], newTopLineNum - lastLineNum, True); 3221 /* printf("counting forward %d lines from start of last line\n", 3222 newTopLineNum - lastLineNum); */ 3223 } else { 3224 textD->firstChar = TextDCountBackwardNLines(textD, buf->length, 3225 textD->nBufferLines - newTopLineNum + 1); 3226 /* printf("counting backward %d lines from end\n", 3227 textD->nBufferLines - newTopLineNum + 1); */ 3228 } 3229 3230 /* Fill in the line starts array */ 3231 if (lineDelta < 0 && -lineDelta < nVisLines) { 3232 for (i=nVisLines-1; i >= -lineDelta; i--) 3233 lineStarts[i] = lineStarts[i+lineDelta]; 3234 calcLineStarts(textD, 0, -lineDelta); 3235 } else if (lineDelta > 0 && lineDelta < nVisLines) { 3236 for (i=0; i<nVisLines-lineDelta; i++) 3237 lineStarts[i] = lineStarts[i+lineDelta]; 3238 calcLineStarts(textD, nVisLines-lineDelta, nVisLines-1); 3239 } else 3240 calcLineStarts(textD, 0, nVisLines); 3241 3242 /* Set lastChar and topLineNum */ 3243 calcLastChar(textD); 3244 textD->topLineNum = newTopLineNum; 3245 3246 /* If we're numbering lines or being asked to maintain an absolute line 3247 number, re-calculate the absolute line number */ 3248 offsetAbsLineNum(textD, oldFirstChar); 3249 3250 /* { int i; 3251 printf("lineStarts After: "); 3252 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]); 3253 printf("\n"); 3254 } */ 3255 } 3256 3257 /* 3258 ** Update the line starts array, topLineNum, firstChar and lastChar for text 3259 ** display "textD" after a modification to the text buffer, given by the 3260 ** position where the change began "pos", and the nmubers of characters 3261 ** and lines inserted and deleted. 3262 */ 3263 static void updateLineStarts(textDisp *textD, int pos, int charsInserted, 3264 int charsDeleted, int linesInserted, int linesDeleted, int *scrolled) 3265 { 3266 int *lineStarts = textD->lineStarts; 3267 int i, lineOfPos, lineOfEnd, nVisLines = textD->nVisibleLines; 3268 int charDelta = charsInserted - charsDeleted; 3269 int lineDelta = linesInserted - linesDeleted; 3270 3271 /* { int i; 3272 printf("linesDeleted %d, linesInserted %d, charsInserted %d, charsDeleted %d\n", 3273 linesDeleted, linesInserted, charsInserted, charsDeleted); 3274 printf("lineStarts Before: "); 3275 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]); 3276 printf("\n"); 3277 } */ 3278 /* If all of the changes were before the displayed text, the display 3279 doesn't change, just update the top line num and offset the line 3280 start entries and first and last characters */ 3281 if (pos + charsDeleted < textD->firstChar) { 3282 textD->topLineNum += lineDelta; 3283 for (i=0; i<nVisLines && lineStarts[i] != -1; i++) 3284 lineStarts[i] += charDelta; 3285 /* { int i; 3286 printf("lineStarts after delete doesn't touch: "); 3287 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]); 3288 printf("\n"); 3289 } */ 3290 textD->firstChar += charDelta; 3291 textD->lastChar += charDelta; 3292 *scrolled = False; 3293 return; 3294 } 3295 3296 /* The change began before the beginning of the displayed text, but 3297 part or all of the displayed text was deleted */ 3298 if (pos < textD->firstChar) { 3299 /* If some text remains in the window, anchor on that */ 3300 if (posToVisibleLineNum(textD, pos + charsDeleted, &lineOfEnd) && 3301 ++lineOfEnd < nVisLines && lineStarts[lineOfEnd] != -1) { 3302 textD->topLineNum = max(1, textD->topLineNum + lineDelta); 3303 textD->firstChar = TextDCountBackwardNLines(textD, 3304 lineStarts[lineOfEnd] + charDelta, lineOfEnd); 3305 /* Otherwise anchor on original line number and recount everything */ 3306 } else { 3307 if (textD->topLineNum > textD->nBufferLines + lineDelta) { 3308 textD->topLineNum = 1; 3309 textD->firstChar = 0; 3310 } else 3311 textD->firstChar = TextDCountForwardNLines(textD, 0, 3312 textD->topLineNum - 1, True); 3313 } 3314 calcLineStarts(textD, 0, nVisLines-1); 3315 /* { int i; 3316 printf("lineStarts after delete encroaches: "); 3317 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]); 3318 printf("\n"); 3319 } */ 3320 /* calculate lastChar by finding the end of the last displayed line */ 3321 calcLastChar(textD); 3322 *scrolled = True; 3323 return; 3324 } 3325 3326 /* If the change was in the middle of the displayed text (it usually is), 3327 salvage as much of the line starts array as possible by moving and 3328 offsetting the entries after the changed area, and re-counting the 3329 added lines or the lines beyond the salvaged part of the line starts 3330 array */ 3331 if (pos <= textD->lastChar) { 3332 /* find line on which the change began */ 3333 posToVisibleLineNum(textD, pos, &lineOfPos); 3334 /* salvage line starts after the changed area */ 3335 if (lineDelta == 0) { 3336 for (i=lineOfPos+1; i<nVisLines && lineStarts[i]!= -1; i++) 3337 lineStarts[i] += charDelta; 3338 } else if (lineDelta > 0) { 3339 for (i=nVisLines-1; i>=lineOfPos+lineDelta+1; i--) 3340 lineStarts[i] = lineStarts[i-lineDelta] + 3341 (lineStarts[i-lineDelta] == -1 ? 0 : charDelta); 3342 } else /* (lineDelta < 0) */ { 3343 for (i=max(0,lineOfPos+1); i<nVisLines+lineDelta; i++) 3344 lineStarts[i] = lineStarts[i-lineDelta] + 3345 (lineStarts[i-lineDelta] == -1 ? 0 : charDelta); 3346 } 3347 /* { int i; 3348 printf("lineStarts after salvage: "); 3349 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]); 3350 printf("\n"); 3351 } */ 3352 /* fill in the missing line starts */ 3353 if (linesInserted >= 0) 3354 calcLineStarts(textD, lineOfPos + 1, lineOfPos + linesInserted); 3355 if (lineDelta < 0) 3356 calcLineStarts(textD, nVisLines+lineDelta, nVisLines); 3357 /* { int i; 3358 printf("lineStarts after recalculation: "); 3359 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]); 3360 printf("\n"); 3361 } */ 3362 /* calculate lastChar by finding the end of the last displayed line */ 3363 calcLastChar(textD); 3364 *scrolled = False; 3365 return; 3366 } 3367 3368 /* Change was past the end of the displayed text, but displayable by virtue 3369 of being an insert at the end of the buffer into visible blank lines */ 3370 if (emptyLinesVisible(textD)) { 3371 posToVisibleLineNum(textD, pos, &lineOfPos); 3372 calcLineStarts(textD, lineOfPos, lineOfPos+linesInserted); 3373 calcLastChar(textD); 3374 /* { int i; 3375 printf("lineStarts after insert at end: "); 3376 for(i=0; i<nVisLines; i++) printf("%d ", lineStarts[i]); 3377 printf("\n"); 3378 } */ 3379 *scrolled = False; 3380 return; 3381 } 3382 3383 /* Change was beyond the end of the buffer and not visible, do nothing */ 3384 *scrolled = False; 3385 } 3386 3387 /* 3388 ** Scan through the text in the "textD"'s buffer and recalculate the line 3389 ** starts array values beginning at index "startLine" and continuing through 3390 ** (including) "endLine". It assumes that the line starts entry preceding 3391 ** "startLine" (or textD->firstChar if startLine is 0) is good, and re-counts 3392 ** newlines to fill in the requested entries. Out of range values for 3393 ** "startLine" and "endLine" are acceptable. 3394 */ 3395 static void calcLineStarts(textDisp *textD, int startLine, int endLine) 3396 { 3397 int startPos, bufLen = textD->buffer->length; 3398 int line, lineEnd, nextLineStart, nVis = textD->nVisibleLines; 3399 int *lineStarts = textD->lineStarts; 3400 3401 /* Clean up (possibly) messy input parameters */ 3402 if (nVis == 0) return; 3403 if (endLine < 0) endLine = 0; 3404 if (endLine >= nVis) endLine = nVis - 1; 3405 if (startLine < 0) startLine = 0; 3406 if (startLine >=nVis) startLine = nVis - 1; 3407 if (startLine > endLine) 3408 return; 3409 3410 /* Find the last known good line number -> position mapping */ 3411 if (startLine == 0) { 3412 lineStarts[0] = textD->firstChar; 3413 startLine = 1; 3414 } 3415 startPos = lineStarts[startLine-1]; 3416 3417 /* If the starting position is already past the end of the text, 3418 fill in -1's (means no text on line) and return */ 3419 if (startPos == -1) { 3420 for (line=startLine; line<=endLine; line++) 3421 lineStarts[line] = -1; 3422 return; 3423 } 3424 3425 /* Loop searching for ends of lines and storing the positions of the 3426 start of the next line in lineStarts */ 3427 for (line=startLine; line<=endLine; line++) { 3428 findLineEnd(textD, startPos, True, &lineEnd, &nextLineStart); 3429 startPos = nextLineStart; 3430 if (startPos >= bufLen) { 3431 /* If the buffer ends with a newline or line break, put 3432 buf->length in the next line start position (instead of 3433 a -1 which is the normal marker for an empty line) to 3434 indicate that the cursor may safely be displayed there */ 3435 if (line == 0 || (lineStarts[line-1] != bufLen && 3436 lineEnd != nextLineStart)) { 3437 lineStarts[line] = bufLen; 3438 line++; 3439 } 3440 break; 3441 } 3442 lineStarts[line] = startPos; 3443 } 3444 3445 /* Set any entries beyond the end of the text to -1 */ 3446 for (; line<=endLine; line++) 3447 lineStarts[line] = -1; 3448 } 3449 3450 /* 3451 ** Given a textDisp with a complete, up-to-date lineStarts array, update 3452 ** the lastChar entry to point to the last buffer position displayed. 3453 */ 3454 static void calcLastChar(textDisp *textD) 3455 { 3456 int i; 3457 3458 for (i=textD->nVisibleLines-1; i>0 && textD->lineStarts[i]== -1; i--); 3459 textD->lastChar = i < 0 ? 0 : 3460 TextDEndOfLine(textD, textD->lineStarts[i], True); 3461 } 3462 3463 void TextDImposeGraphicsExposeTranslation(textDisp *textD, int *xOffset, int *yOffset) 3464 { 3465 if (textD->graphicsExposeQueue) { 3466 graphicExposeTranslationEntry *thisGEQEntry = textD->graphicsExposeQueue->next; 3467 if (thisGEQEntry) { 3468 *xOffset += thisGEQEntry->horizontal; 3469 *yOffset += thisGEQEntry->vertical; 3470 } 3471 } 3472 } 3473 3474 Boolean TextDPopGraphicExposeQueueEntry(textDisp *textD) 3475 { 3476 graphicExposeTranslationEntry *removedGEQEntry = textD->graphicsExposeQueue; 3477 3478 if (removedGEQEntry) { 3479 textD->graphicsExposeQueue = removedGEQEntry->next; 3480 NEditFree(removedGEQEntry); 3481 } 3482 return(removedGEQEntry?True:False); 3483 } 3484 3485 void TextDTranlateGraphicExposeQueue(textDisp *textD, int xOffset, int yOffset, Boolean appendEntry) 3486 { 3487 graphicExposeTranslationEntry *newGEQEntry = NULL; 3488 if (appendEntry) { 3489 newGEQEntry = (graphicExposeTranslationEntry *)NEditMalloc(sizeof(graphicExposeTranslationEntry)); 3490 newGEQEntry->next = NULL; 3491 newGEQEntry->horizontal = xOffset; 3492 newGEQEntry->vertical = yOffset; 3493 } 3494 if (textD->graphicsExposeQueue) { 3495 graphicExposeTranslationEntry *iter = textD->graphicsExposeQueue; 3496 while (iter->next) { 3497 iter->next->horizontal += xOffset; 3498 iter->next->vertical += yOffset; 3499 iter = iter->next; 3500 } 3501 if (appendEntry) { 3502 iter->next = (struct graphicExposeTranslationEntry *)newGEQEntry; 3503 } 3504 } 3505 else { 3506 if (appendEntry) { 3507 textD->graphicsExposeQueue = newGEQEntry; 3508 } 3509 } 3510 } 3511 3512 static void setScroll(textDisp *textD, int topLineNum, int horizOffset, 3513 int updateVScrollBar, int updateHScrollBar) 3514 { 3515 int fontHeight = textD->ascent + textD->descent; 3516 int origHOffset = textD->horizOffset; 3517 int lineDelta = textD->topLineNum - topLineNum; 3518 int xOffset, yOffset, srcX, srcY, dstX, dstY, width, height; 3519 int exactHeight = textD->height - textD->height % 3520 (textD->ascent + textD->descent); 3521 3522 /* Do nothing if scroll position hasn't actually changed or there's no 3523 window to draw in yet */ 3524 if (XtWindow(textD->w) == 0 || (textD->horizOffset == horizOffset && 3525 textD->topLineNum == topLineNum)) 3526 return; 3527 3528 /* If part of the cursor is protruding beyond the text clipping region, 3529 clear it off */ 3530 blankCursorProtrusions(textD); 3531 3532 /* If the vertical scroll position has changed, update the line 3533 starts array and related counters in the text display */ 3534 offsetLineStarts(textD, topLineNum); 3535 3536 /* Just setting textD->horizOffset is enough information for redisplay */ 3537 textD->horizOffset = horizOffset; 3538 3539 /* Update the scroll bar positions if requested, note: updating the 3540 horizontal scroll bars can have the further side-effect of changing 3541 the horizontal scroll position, textD->horizOffset */ 3542 if (updateVScrollBar && textD->vScrollBar != NULL) { 3543 updateVScrollBarRange(textD); 3544 } 3545 if (updateHScrollBar && textD->hScrollBar != NULL) { 3546 updateHScrollBarRange(textD); 3547 } 3548 3549 /* Redisplay everything if the window is partially obscured (since 3550 it's too hard to tell what displayed areas are salvageable) or 3551 if there's nothing to recover because the scroll distance is large */ 3552 xOffset = origHOffset - textD->horizOffset; 3553 yOffset = lineDelta * fontHeight; 3554 //if (textD->visibility != VisibilityUnobscured || 3555 // abs(xOffset) > textD->width || abs(yOffset) > exactHeight) { 3556 if(1) { 3557 // WORKAROUND for hscroll bug with new xft rendering 3558 // the code with XCopyArea doesn't work, so we always redisplay 3559 TextDTranlateGraphicExposeQueue(textD, xOffset, yOffset, False); 3560 TextDRedisplayRect(textD, textD->left, textD->top, textD->width, 3561 textD->height); 3562 } else { 3563 /* If the window is not obscured, paint most of the window using XCopyArea 3564 from existing displayed text, and redraw only what's necessary */ 3565 /* Recover the useable window areas by moving to the proper location */ 3566 srcX = textD->left + (xOffset >= 0 ? 0 : -xOffset); 3567 dstX = textD->left + (xOffset >= 0 ? xOffset : 0); 3568 width = textD->width - abs(xOffset); 3569 srcY = textD->top + (yOffset >= 0 ? 0 : -yOffset); 3570 dstY = textD->top + (yOffset >= 0 ? yOffset : 0); 3571 height = exactHeight - abs(yOffset); 3572 resetClipRectangles(textD); 3573 TextDTranlateGraphicExposeQueue(textD, xOffset, yOffset, True); 3574 XCopyArea(XtDisplay(textD->w), XtWindow(textD->w), XtWindow(textD->w), 3575 textD->gc, srcX, srcY, width, height, dstX, dstY); 3576 /* redraw the un-recoverable parts */ 3577 if (yOffset > 0) { 3578 TextDRedisplayRect(textD, textD->left, textD->top, 3579 textD->width, yOffset); 3580 } 3581 else if (yOffset < 0) { 3582 TextDRedisplayRect(textD, textD->left, textD->top + 3583 textD->height + yOffset, textD->width, -yOffset); 3584 } 3585 if (xOffset > 0) { 3586 TextDRedisplayRect(textD, textD->left, textD->top, 3587 xOffset, textD->height); 3588 } 3589 else if (xOffset < 0) { 3590 TextDRedisplayRect(textD, textD->left + textD->width + xOffset, 3591 textD->top, -xOffset, textD->height); 3592 } 3593 /* Restore protruding parts of the cursor */ 3594 int left, right; 3595 TextDCursorLR(textD, &left, &right); 3596 textDRedisplayRange(textD, left, right); 3597 } 3598 3599 /* Refresh line number/calltip display if its up and we've scrolled 3600 vertically */ 3601 if (lineDelta != 0) { 3602 redrawLineNumbers(textD, textD->top, textD->height, True); 3603 TextDRedrawCalltip(textD, 0); 3604 } 3605 3606 HandleAllPendingGraphicsExposeNoExposeEvents((TextWidget)textD->w, NULL); 3607 } 3608 3609 /* 3610 ** Update the minimum, maximum, slider size, page increment, and value 3611 ** for vertical scroll bar. 3612 */ 3613 static void updateVScrollBarRange(textDisp *textD) 3614 { 3615 int sliderSize, sliderMax, sliderValue; 3616 3617 if (textD->vScrollBar == NULL) 3618 return; 3619 3620 /* The Vert. scroll bar value and slider size directly represent the top 3621 line number, and the number of visible lines respectively. The scroll 3622 bar maximum value is chosen to generally represent the size of the whole 3623 buffer, with minor adjustments to keep the scroll bar widget happy */ 3624 sliderSize = max(textD->nVisibleLines, 1); /* Avoid X warning (size < 1) */ 3625 sliderValue = textD->topLineNum; 3626 sliderMax = max(textD->nBufferLines + 2 + 3627 TEXT_OF_TEXTD(textD).cursorVPadding, 3628 sliderSize + sliderValue); 3629 XtVaSetValues(textD->vScrollBar, 3630 XmNmaximum, sliderMax, 3631 XmNsliderSize, sliderSize, 3632 XmNpageIncrement, max(1, textD->nVisibleLines - 1), 3633 XmNvalue, sliderValue, NULL); 3634 } 3635 3636 /* 3637 ** Update the minimum, maximum, slider size, page increment, and value 3638 ** for the horizontal scroll bar. If scroll position is such that there 3639 ** is blank space to the right of all lines of text, scroll back (adjust 3640 ** horizOffset but don't redraw) to take up the slack and position the 3641 ** right edge of the text at the right edge of the display. 3642 ** 3643 ** Note, there is some cost to this routine, since it scans the whole range 3644 ** of displayed text, particularly since it's usually called for each typed 3645 ** character! 3646 */ 3647 static int updateHScrollBarRange(textDisp *textD) 3648 { 3649 int i, maxWidth = 0, sliderMax, sliderWidth; 3650 int origHOffset = textD->horizOffset; 3651 3652 if (textD->hScrollBar == NULL || !XtIsManaged(textD->hScrollBar)) 3653 return False; 3654 3655 /* Scan all the displayed lines to find the width of the longest line */ 3656 for (i=0; i<textD->nVisibleLines && textD->lineStarts[i]!= -1; i++) 3657 maxWidth = max(measureVisLine(textD, i), maxWidth); 3658 3659 /* If the scroll position is beyond what's necessary to keep all lines 3660 in view, scroll to the left to bring the end of the longest line to 3661 the right margin */ 3662 if (maxWidth < textD->width + textD->horizOffset && textD->horizOffset > 0) 3663 textD->horizOffset = max(0, maxWidth - textD->width); 3664 3665 /* Readjust the scroll bar */ 3666 sliderWidth = textD->width; 3667 sliderMax = max(maxWidth, sliderWidth + textD->horizOffset); 3668 XtVaSetValues(textD->hScrollBar, 3669 XmNmaximum, sliderMax, 3670 XmNsliderSize, sliderWidth, 3671 XmNpageIncrement, max(textD->width - 100, 10), 3672 XmNvalue, textD->horizOffset, NULL); 3673 3674 /* Return True if scroll position was changed */ 3675 return origHOffset != textD->horizOffset; 3676 } 3677 3678 /* 3679 ** Define area for drawing line numbers. A width of 0 disables line 3680 ** number drawing. 3681 */ 3682 void TextDSetLineNumberArea(textDisp *textD, int lineNumLeft, int lineNumWidth, 3683 int textLeft) 3684 { 3685 int newWidth = textD->width + textD->left - textLeft; 3686 textD->lineNumLeft = lineNumLeft; 3687 textD->lineNumWidth = lineNumWidth; 3688 textD->left = textLeft; 3689 XClearWindow(XtDisplay(textD->w), XtWindow(textD->w)); 3690 resetAbsLineNum(textD); 3691 TextDResize(textD, newWidth, textD->height); 3692 TextDRedisplayRect(textD, 0, textD->top, INT_MAX, textD->height); 3693 } 3694 3695 /* 3696 ** Refresh the line number area. If clearAll is False, writes only over 3697 ** the character cell areas. Setting clearAll to True will clear out any 3698 ** stray marks outside of the character cell area, which might have been 3699 ** left from before a resize or font change. 3700 */ 3701 static void redrawLineNumbers(textDisp *textD, int top, int height, int clearAll) 3702 { 3703 int y, line, visLine, nCols, lineStart; 3704 char lineNumString[12]; 3705 int lineHeight = textD->ascent + textD->descent; 3706 int charWidth = textD->font->maxWidth; 3707 XRectangle clipRect; 3708 3709 /* Don't draw if lineNumWidth == 0 (line numbers are hidden), or widget is 3710 not yet realized */ 3711 if (textD->lineNumWidth == 0 || XtWindow(textD->w) == 0) 3712 return; 3713 3714 /* Make sure we reset the clipping range for the line numbers GC, because 3715 the GC may be shared (eg, if the line numbers and text have the same 3716 color) and therefore the clipping ranges may be invalid. */ 3717 clipRect.x = 0; //textD->lineNumLeft; 3718 clipRect.y = 0; //textD->top; 3719 clipRect.width = textD->lineNumWidth + textD->lineNumLeft; 3720 clipRect.height = textD->height + 2*textD->top; 3721 //XSetClipRectangles(display, textD->lineNumGC, 0, 0, 3722 // &clipRect, 1, Unsorted); 3723 if(textD->d) { 3724 XftDrawSetClipRectangles(textD->d, 0, 0, &clipRect, 1); 3725 } 3726 3727 /* Erase the previous contents of the line number area, if requested */ 3728 if (clearAll) { 3729 //XClearArea(XtDisplay(textD->w), XtWindow(textD->w), textD->lineNumLeft, 3730 // textD->top, textD->lineNumWidth, textD->height, False); 3731 XftDrawRect( 3732 textD->d, 3733 &textD->colorProfile->lineNoBgColor, 3734 0, 3735 0, 3736 textD->lineNumLeft + textD->lineNumWidth, 3737 2*top + height); 3738 } 3739 3740 /* Draw the line numbers, aligned to the text */ 3741 nCols = min(11, textD->lineNumWidth / charWidth); 3742 y = textD->top; 3743 line = getAbsTopLineNum(textD); 3744 for (visLine=0; visLine < textD->nVisibleLines; visLine++) { 3745 lineStart = textD->lineStarts[visLine]; 3746 if (lineStart != -1 && (lineStart==0 || 3747 BufGetCharacter(textD->buffer, lineStart-1)=='\n')) { 3748 snprintf(lineNumString, 12, "%*d", nCols, line); 3749 XftDrawString8( 3750 textD->d, 3751 &textD->colorProfile->lineNoFgColor, 3752 FontDefault(textD->font), 3753 textD->lineNumLeft, 3754 y + textD->ascent, 3755 (FcChar8*)lineNumString, 3756 strlen(lineNumString)); 3757 line++; 3758 } else { 3759 /* 3760 XClearArea(XtDisplay(textD->w), XtWindow(textD->w), 3761 textD->lineNumLeft, y, textD->lineNumWidth, 3762 textD->ascent + textD->descent, False); 3763 */ 3764 if (visLine == 0) 3765 line++; 3766 } 3767 y += lineHeight; 3768 } 3769 } 3770 3771 /* 3772 ** Callbacks for drag or valueChanged on scroll bars 3773 */ 3774 static void vScrollCB(Widget w, XtPointer clientData, XtPointer callData) 3775 { 3776 textDisp *textD = (textDisp *)clientData; 3777 int newValue = ((XmScrollBarCallbackStruct *)callData)->value; 3778 int lineDelta = newValue - textD->topLineNum; 3779 3780 if (lineDelta == 0) 3781 return; 3782 setScroll(textD, newValue, textD->horizOffset, False, True); 3783 } 3784 static void hScrollCB(Widget w, XtPointer clientData, XtPointer callData) 3785 { 3786 textDisp *textD = (textDisp *)clientData; 3787 int newValue = ((XmScrollBarCallbackStruct *)callData)->value; 3788 3789 if (newValue == textD->horizOffset) 3790 return; 3791 setScroll(textD, textD->topLineNum, newValue, False, False); 3792 } 3793 3794 static void visibilityEH(Widget w, XtPointer data, XEvent *event, 3795 Boolean *continueDispatch) 3796 { 3797 /* Record whether the window is fully visible or not. This information 3798 is used for choosing the scrolling methodology for optimal performance, 3799 if the window is partially obscured, XCopyArea may not work */ 3800 ((textDisp *)data)->visibility = ((XVisibilityEvent *)event)->state; 3801 } 3802 3803 static int max(int i1, int i2) 3804 { 3805 return i1 >= i2 ? i1 : i2; 3806 } 3807 3808 static int min(int i1, int i2) 3809 { 3810 return i1 <= i2 ? i1 : i2; 3811 } 3812 3813 /* 3814 ** Count the number of newlines in a null-terminated text string; 3815 */ 3816 static int countLines(const char *string) 3817 { 3818 const char *c; 3819 int lineCount = 0; 3820 3821 if (string == NULL) 3822 return 0; 3823 for (c=string; *c!='\0'; c++) 3824 if (*c == '\n') lineCount++; 3825 return lineCount; 3826 } 3827 3828 /* 3829 ** Return the width in pixels of the displayed line pointed to by "visLineNum" 3830 */ 3831 static int measureVisLine(textDisp *textD, int visLineNum) 3832 { 3833 textBuffer *buf = textD->buffer; 3834 int i, width = 0, style, lineLen = visLineLength(textD, visLineNum); 3835 int lineStartPos = textD->lineStarts[visLineNum]; 3836 char *free_lineStr; 3837 const char *lineStr = BufGetRange2(buf, lineStartPos, lineStartPos + lineLen, &free_lineStr); 3838 FcChar32 expandedChar[MAX_EXP_CHAR_LEN]; 3839 FcChar32 uc; 3840 int inc; 3841 int charLen; 3842 unsigned short indent = 0; 3843 NFont *font; 3844 3845 for(i=0;i<lineLen;i+=inc) { 3846 inc = getCharWidth(textD, lineStr+i, &uc, lineLen - i); 3847 if(inc > 1) { 3848 charLen = 1; 3849 expandedChar[0] = uc; 3850 indent += inc; 3851 } else { 3852 charLen = BufExpandCharacter4(lineStr[i], 3853 indent, 3854 expandedChar, 3855 buf->tabDist, buf->nullSubsChar); 3856 indent += charLen; 3857 } 3858 3859 if (textD->styleBuffer) { 3860 style = (unsigned char)BufGetCharacter(textD->styleBuffer, 3861 lineStartPos+i) - ASCII_A; 3862 font = textD->styleTable[style].font; 3863 } else { 3864 font = textD->font; 3865 } 3866 3867 width += charWidth4(textD, expandedChar, charLen, font); 3868 } 3869 NEditFree(free_lineStr); 3870 return width; 3871 } 3872 3873 /* 3874 ** Return true if there are lines visible with no corresponding buffer text 3875 */ 3876 static int emptyLinesVisible(textDisp *textD) 3877 { 3878 return textD->nVisibleLines > 0 && 3879 textD->lineStarts[textD->nVisibleLines-1] == -1; 3880 } 3881 3882 /* 3883 ** When the cursor is at the left or right edge of the text, part of it 3884 ** sticks off into the clipped region beyond the text. Normal redrawing 3885 ** can not overwrite this protruding part of the cursor, so it must be 3886 ** erased independently by calling this routine. 3887 */ 3888 static void blankSingleCursorProtrusions(textDisp *textD) 3889 { 3890 int x, width, cursorX = textD->cursor->x, cursorY = textD->cursor->y; 3891 int fontWidth = textD->font->maxWidth; //FontDefault(textD->font)->max_advance_width; 3892 int fontHeight = textD->ascent + textD->descent; 3893 int cursorWidth, left = textD->left, right = left + textD->width; 3894 3895 cursorWidth = (fontWidth/3) * 2; 3896 if (cursorX >= left-1 && cursorX <= left + cursorWidth/2 - 1) { 3897 x = cursorX - cursorWidth/2; 3898 width = left - x; 3899 } else if (cursorX >= right - cursorWidth/2 && cursorX <= right) { 3900 x = right; 3901 width = cursorX + cursorWidth/2 + 2 - right; 3902 } else 3903 return; 3904 3905 XClearArea(XtDisplay(textD->w), XtWindow(textD->w), x, cursorY, 3906 width, fontHeight, False); 3907 } 3908 3909 static void blankCursorProtrusions(textDisp *textD) { 3910 if(textD->mcursorSize == 1) { 3911 blankSingleCursorProtrusions(textD); 3912 } else { 3913 textCursor *origCursor = textD->cursor; 3914 for(int i=0;i<textD->mcursorSize;i++) { 3915 textD->cursor = textD->multicursor + i; 3916 blankSingleCursorProtrusions(textD); 3917 } 3918 textD->cursor = origCursor; 3919 } 3920 } 3921 3922 /* 3923 ** Allocate shared graphics contexts used by the widget, which must be 3924 ** re-allocated on a font change. 3925 */ 3926 static void allocateFixedFontGCs(textDisp *textD, Pixel bgPixel, Pixel fgPixel) 3927 { 3928 textD->gc = allocateGC(textD->w, GCForeground | GCBackground, 3929 fgPixel, bgPixel, 0, GCClipMask, GCArcMode); 3930 } 3931 3932 /* 3933 ** X11R4 does not have the XtAllocateGC function for sharing graphics contexts 3934 ** with changeable fields. Unfortunately the R4 call for creating shared 3935 ** graphics contexts (XtGetGC) is rarely useful because most widgets need 3936 ** to be able to set and change clipping, and that makes the GC unshareable. 3937 ** 3938 ** This function allocates and returns a gc, using XtAllocateGC if possible, 3939 ** or XCreateGC on X11R4 systems where XtAllocateGC is not available. 3940 */ 3941 static GC allocateGC(Widget w, unsigned long valueMask, 3942 unsigned long foreground, unsigned long background, Font font, 3943 unsigned long dynamicMask, unsigned long dontCareMask) 3944 { 3945 XGCValues gcValues; 3946 3947 gcValues.font = font; 3948 gcValues.background = background; 3949 gcValues.foreground = foreground; 3950 #if defined(XlibSpecificationRelease) && XlibSpecificationRelease > 4 3951 return XtAllocateGC(w, 0, valueMask, &gcValues, dynamicMask, 3952 dontCareMask); 3953 #else 3954 return XCreateGC(XtDisplay(w), RootWindowOfScreen(XtScreen(w)), 3955 valueMask, &gcValues); 3956 #endif 3957 } 3958 3959 /* 3960 ** Release a gc allocated with allocateGC above 3961 */ 3962 static void releaseGC(Widget w, GC gc) 3963 { 3964 #if defined(XlibSpecificationRelease) && XlibSpecificationRelease > 4 3965 XtReleaseGC(w, gc); 3966 #else 3967 XFreeGC(XtDisplay(w), gc); 3968 #endif 3969 } 3970 3971 /* 3972 ** resetClipRectangles sets the clipping rectangles for GCs which clip 3973 ** at the text boundary (as opposed to the window boundary). These GCs 3974 ** are shared such that the drawing styles are constant, but the clipping 3975 ** rectangles are allowed to change among different users of the GCs (the 3976 ** GCs were created with XtAllocGC). This routine resets them so the clipping 3977 ** rectangles are correct for this text display. 3978 */ 3979 static void resetClipRectangles(textDisp *textD) 3980 { 3981 XRectangle clipRect; 3982 Display *display = XtDisplay(textD->w); 3983 3984 clipRect.x = textD->left; 3985 clipRect.y = textD->top; 3986 clipRect.width = textD->width; 3987 clipRect.height = textD->height - textD->height % 3988 (textD->ascent + textD->descent); 3989 3990 XSetClipRectangles(display, textD->gc, 0, 0, 3991 &clipRect, 1, Unsorted); 3992 3993 if(textD->d) { 3994 XftDrawSetClipRectangles(textD->d, 0, 0, &clipRect, 1); 3995 } 3996 } 3997 3998 /* 3999 ** Return the length of a line (number of displayable characters) by examining 4000 ** entries in the line starts array rather than by scanning for newlines 4001 */ 4002 static int visLineLength(textDisp *textD, int visLineNum) 4003 { 4004 int nextLineStart, lineStartPos = textD->lineStarts[visLineNum]; 4005 4006 if (lineStartPos == -1) 4007 return 0; 4008 if (visLineNum+1 >= textD->nVisibleLines) 4009 return textD->lastChar - lineStartPos; 4010 nextLineStart = textD->lineStarts[visLineNum+1]; 4011 if (nextLineStart == -1) 4012 return textD->lastChar - lineStartPos; 4013 if (wrapUsesCharacter(textD, nextLineStart-1)) 4014 return nextLineStart-1 - lineStartPos; 4015 return nextLineStart - lineStartPos; 4016 } 4017 4018 /* 4019 ** When continuous wrap is on, and the user inserts or deletes characters, 4020 ** wrapping can happen before and beyond the changed position. This routine 4021 ** finds the extent of the changes, and counts the deleted and inserted lines 4022 ** over that range. It also attempts to minimize the size of the range to 4023 ** what has to be counted and re-displayed, so the results can be useful 4024 ** both for delimiting where the line starts need to be recalculated, and 4025 ** for deciding what part of the text to redisplay. 4026 */ 4027 static int findWrapRange(textDisp *textD, const char *deletedText, int pos, 4028 int nInserted, int nDeleted, int *modRangeStart, int *modRangeEnd, 4029 int *linesInserted, int *linesDeleted) 4030 { 4031 int length, retPos, retLines, retLineStart, retLineEnd; 4032 textBuffer *deletedTextBuf, *buf = textD->buffer; 4033 int nVisLines = textD->nVisibleLines; 4034 int *lineStarts = textD->lineStarts; 4035 int countFrom, countTo, lineStart, adjLineStart, i; 4036 int visLineNum = 0, nLines = 0; 4037 int nl = 0; 4038 4039 /* 4040 ** Determine where to begin searching: either the previous newline, or 4041 ** if possible, limit to the start of the (original) previous displayed 4042 ** line, using information from the existing line starts array 4043 */ 4044 if (pos >= textD->firstChar && pos <= textD->lastChar) { 4045 for (i=nVisLines-1; i>0; i--) { 4046 if (lineStarts[i] != -1 && pos >= lineStarts[i]) 4047 break; 4048 } 4049 if (i > 0) { 4050 countFrom = lineStarts[i-1]; 4051 visLineNum = i-1; 4052 } else 4053 countFrom = BufStartOfLine(buf, pos); 4054 } else 4055 countFrom = BufStartOfLine(buf, pos); 4056 4057 4058 /* 4059 ** Move forward through the (new) text one line at a time, counting 4060 ** displayed lines, and looking for either a real newline, or for the 4061 ** line starts to re-sync with the original line starts array 4062 */ 4063 lineStart = countFrom; 4064 *modRangeStart = countFrom; 4065 while (True) { 4066 4067 /* advance to the next line. If the line ended in a real newline 4068 or the end of the buffer, that's far enough */ 4069 wrappedLineCounter(textD, buf, lineStart, buf->length, 1, True, 0, 4070 &retPos, &retLines, &retLineStart, &retLineEnd, NULL); 4071 if(pos == retLineEnd) { 4072 nl = 1; 4073 } 4074 if (retPos >= buf->length) { 4075 countTo = buf->length; 4076 *modRangeEnd = countTo; 4077 if (retPos != retLineEnd) 4078 nLines++; 4079 break; 4080 } else 4081 lineStart = retPos; 4082 nLines++; 4083 if (lineStart > pos + nInserted && 4084 BufGetCharacter(buf, lineStart-1) == '\n') { 4085 countTo = lineStart; 4086 *modRangeEnd = lineStart; 4087 break; 4088 } 4089 4090 /* Don't try to resync in continuous wrap mode with non-fixed font 4091 sizes; it would result in a chicken-and-egg dependency between 4092 the calculations for the inserted and the deleted lines. 4093 If we're in that mode, the number of deleted lines is calculated in 4094 advance, without resynchronization, so we shouldn't resynchronize 4095 for the inserted lines either. */ 4096 if (textD->suppressResync) 4097 continue; 4098 4099 /* check for synchronization with the original line starts array 4100 before pos, if so, the modified range can begin later */ 4101 if (lineStart <= pos) { 4102 while (visLineNum<nVisLines && lineStarts[visLineNum] < lineStart) 4103 visLineNum++; 4104 if (visLineNum < nVisLines && lineStarts[visLineNum] == lineStart) { 4105 countFrom = lineStart; 4106 nLines = 0; 4107 if (visLineNum+1 < nVisLines && lineStarts[visLineNum+1] != -1) 4108 *modRangeStart = min(pos, lineStarts[visLineNum+1]-1); 4109 else 4110 *modRangeStart = countFrom; 4111 } else 4112 *modRangeStart = min(*modRangeStart, lineStart-1); 4113 } 4114 4115 /* check for synchronization with the original line starts array 4116 after pos, if so, the modified range can end early */ 4117 else if (lineStart > pos + nInserted) { 4118 adjLineStart = lineStart - nInserted + nDeleted; 4119 while (visLineNum<nVisLines && lineStarts[visLineNum]<adjLineStart) 4120 visLineNum++; 4121 if (visLineNum < nVisLines && lineStarts[visLineNum] != -1 && 4122 lineStarts[visLineNum] == adjLineStart) { 4123 countTo = TextDEndOfLine(textD, lineStart, True); 4124 *modRangeEnd = lineStart; 4125 break; 4126 } 4127 } 4128 } 4129 *linesInserted = nLines; 4130 4131 4132 /* Count deleted lines between countFrom and countTo as the text existed 4133 before the modification (that is, as if the text between pos and 4134 pos+nInserted were replaced by "deletedText"). This extra context is 4135 necessary because wrapping can occur outside of the modified region 4136 as a result of adding or deleting text in the region. This is done by 4137 creating a textBuffer containing the deleted text and the necessary 4138 additional context, and calling the wrappedLineCounter on it. 4139 4140 NOTE: This must not be done in continuous wrap mode when the font 4141 width is not fixed. In that case, the calculation would try 4142 to access style information that is no longer available (deleted 4143 text), or out of date (updated highlighting), possibly leading 4144 to completely wrong calculations and/or even crashes eventually. 4145 (This is not theoretical; it really happened.) 4146 4147 In that case, the calculation of the number of deleted lines 4148 has happened before the buffer was modified (only in that case, 4149 because resynchronization of the line starts is impossible 4150 in that case, which makes the whole calculation less efficient). 4151 */ 4152 if (textD->suppressResync) { 4153 *linesDeleted = textD->nLinesDeleted; 4154 textD->suppressResync = 0; 4155 return nl; 4156 } 4157 4158 length = (pos-countFrom) + nDeleted +(countTo-(pos+nInserted)); 4159 deletedTextBuf = BufCreatePreallocated(length); 4160 if (pos > countFrom) 4161 BufCopyFromBuf(textD->buffer, deletedTextBuf, countFrom, pos, 0); 4162 if (nDeleted != 0) 4163 BufInsert(deletedTextBuf, pos-countFrom, deletedText); 4164 if (countTo > pos+nInserted) 4165 BufCopyFromBuf(textD->buffer, deletedTextBuf, 4166 pos+nInserted, countTo, pos-countFrom+nDeleted); 4167 /* Note that we need to take into account an offset for the style buffer: 4168 the deletedTextBuf can be out of sync with the style buffer. */ 4169 wrappedLineCounter(textD, deletedTextBuf, 0, length, INT_MAX, True, 4170 countFrom, &retPos, &retLines, &retLineStart, &retLineEnd, 4171 NULL); 4172 BufFree(deletedTextBuf); 4173 *linesDeleted = retLines; 4174 textD->suppressResync = 0; 4175 4176 return nl; 4177 } 4178 4179 /* 4180 ** This is a stripped-down version of the findWrapRange() function above, 4181 ** intended to be used to calculate the number of "deleted" lines during 4182 ** a buffer modification. It is called _before_ the modification takes place. 4183 ** 4184 ** This function should only be called in continuous wrap mode with a 4185 ** non-fixed font width. In that case, it is impossible to calculate 4186 ** the number of deleted lines, because the necessary style information 4187 ** is no longer available _after_ the modification. In other cases, we 4188 ** can still perform the calculation afterwards (possibly even more 4189 ** efficiently). 4190 */ 4191 static void measureDeletedLines(textDisp *textD, int pos, int nDeleted) 4192 { 4193 int retPos, retLines, retLineStart, retLineEnd; 4194 textBuffer *buf = textD->buffer; 4195 int nVisLines = textD->nVisibleLines; 4196 int *lineStarts = textD->lineStarts; 4197 int countFrom, lineStart; 4198 int nLines = 0, i; 4199 /* 4200 ** Determine where to begin searching: either the previous newline, or 4201 ** if possible, limit to the start of the (original) previous displayed 4202 ** line, using information from the existing line starts array 4203 */ 4204 if (pos >= textD->firstChar && pos <= textD->lastChar) { 4205 for (i=nVisLines-1; i>0; i--) 4206 if (lineStarts[i] != -1 && pos >= lineStarts[i]) 4207 break; 4208 if (i > 0) { 4209 countFrom = lineStarts[i-1]; 4210 } else 4211 countFrom = BufStartOfLine(buf, pos); 4212 } else 4213 countFrom = BufStartOfLine(buf, pos); 4214 4215 /* 4216 ** Move forward through the (new) text one line at a time, counting 4217 ** displayed lines, and looking for either a real newline, or for the 4218 ** line starts to re-sync with the original line starts array 4219 */ 4220 lineStart = countFrom; 4221 while (True) { 4222 /* advance to the next line. If the line ended in a real newline 4223 or the end of the buffer, that's far enough */ 4224 wrappedLineCounter(textD, buf, lineStart, buf->length, 1, True, 0, 4225 &retPos, &retLines, &retLineStart, &retLineEnd, NULL); 4226 if (retPos >= buf->length) { 4227 if (retPos != retLineEnd) 4228 nLines++; 4229 break; 4230 } else 4231 lineStart = retPos; 4232 nLines++; 4233 if (lineStart > pos + nDeleted && 4234 BufGetCharacter(buf, lineStart-1) == '\n') { 4235 break; 4236 } 4237 4238 /* Unlike in the findWrapRange() function above, we don't try to 4239 resync with the line starts, because we don't know the length 4240 of the inserted text yet, nor the updated style information. 4241 4242 Because of that, we also shouldn't resync with the line starts 4243 after the modification either, because we must perform the 4244 calculations for the deleted and inserted lines in the same way. 4245 4246 This can result in some unnecessary recalculation and redrawing 4247 overhead, and therefore we should only use this two-phase mode 4248 of calculation when it's really needed (continuous wrap + variable 4249 font width). */ 4250 } 4251 textD->nLinesDeleted = nLines; 4252 textD->suppressResync = 1; 4253 } 4254 4255 /* 4256 ** Count forward from startPos to either maxPos or maxLines (whichever is 4257 ** reached first), and return all relevant positions and line count. 4258 ** The provided textBuffer may differ from the actual text buffer of the 4259 ** widget. In that case it must be a (partial) copy of the actual text buffer 4260 ** and the styleBufOffset argument must indicate the starting position of the 4261 ** copy, to take into account the correct style information. 4262 ** 4263 ** Returned values: 4264 ** 4265 ** retPos: Position where counting ended. When counting lines, the 4266 ** position returned is the start of the line "maxLines" 4267 ** lines beyond "startPos". 4268 ** retLines: Number of line breaks counted 4269 ** retLineStart: Start of the line where counting ended 4270 ** retLineEnd: End position of the last line traversed 4271 ** retWrap Was any line wrapped 4272 */ 4273 static void wrappedLineCounter(const textDisp* textD, const textBuffer* buf, 4274 int startPos, int maxPos, int maxLines, 4275 Boolean startPosIsLineStart, int styleBufOffset, 4276 int* retPos, int* retLines, int* retLineStart, int* retLineEnd, 4277 Boolean *retWrap) 4278 { 4279 int lineStart, newLineStart = 0, b, p, colNum, wrapMargin; 4280 int maxWidth, width, countPixels, i, foundBreak; 4281 int nLines = 0, tabDist = textD->buffer->tabDist; 4282 FcChar32 c; 4283 char nullSubsChar = textD->buffer->nullSubsChar; 4284 if(retWrap) { 4285 *retWrap = False; 4286 } 4287 4288 /* If the font is fixed, or there's a wrap margin set, it's more efficient 4289 to measure in columns, than to count pixels. Determine if we can count 4290 in columns (countPixels == False) or must count pixels (countPixels == 4291 True), and set the wrap target for either pixels or columns */ 4292 if (textD->fixedFontWidth != -1 || textD->wrapMargin != 0) { 4293 countPixels = False; 4294 wrapMargin = textD->wrapMargin != 0 ? textD->wrapMargin : 4295 textD->width / textD->fixedFontWidth; 4296 maxWidth = INT_MAX; 4297 } else { 4298 countPixels = True; 4299 wrapMargin = INT_MAX; 4300 maxWidth = textD->width; 4301 } 4302 4303 /* Find the start of the line if the start pos is not marked as a 4304 line start. */ 4305 if (startPosIsLineStart) 4306 lineStart = startPos; 4307 else 4308 lineStart = TextDStartOfLine(textD, startPos); 4309 4310 /* 4311 ** Loop until position exceeds maxPos or line count exceeds maxLines. 4312 ** (actually, contines beyond maxPos to end of line containing maxPos, 4313 ** in case later characters cause a word wrap back before maxPos) 4314 */ 4315 colNum = 0; 4316 width = 0; 4317 int inc = 1; 4318 for (p=lineStart; p<buf->length; p+=inc) { 4319 c = getCharacter32(textD, buf, p, &inc); 4320 4321 /* If the character was a newline, count the line and start over, 4322 otherwise, add it to the width and column counts */ 4323 if ((char)c == '\n') { 4324 if (p >= maxPos) { 4325 *retPos = maxPos; 4326 *retLines = nLines; 4327 *retLineStart = lineStart; 4328 *retLineEnd = maxPos; 4329 return; 4330 } 4331 nLines++; 4332 if (nLines >= maxLines) { 4333 *retPos = p + 1; 4334 *retLines = nLines; 4335 *retLineStart = p + 1; 4336 *retLineEnd = p; 4337 return; 4338 } 4339 lineStart = p + 1; 4340 colNum = 0; 4341 width = 0; 4342 } else if(c != 0) { 4343 colNum += BufCharWidth((char)c, colNum, tabDist, nullSubsChar); 4344 if (countPixels) 4345 width += measurePropChar(textD, c, colNum, p+styleBufOffset); 4346 } // else: c == 0 => invisible escape sequence 4347 4348 /* If character exceeded wrap margin, find the break point 4349 and wrap there */ 4350 if (colNum > wrapMargin || width > maxWidth) { 4351 if(retWrap) { 4352 *retWrap = True; 4353 retWrap = NULL; 4354 } 4355 4356 foundBreak = False; 4357 /* TODO: implement unicode word boundary */ 4358 for (b=p; b>=lineStart; b--) { 4359 c = BufGetCharacter(buf, b); 4360 if ((char)c == '\t' || (char)c == ' ') { 4361 newLineStart = b + 1; 4362 if (countPixels) { 4363 colNum = 0; 4364 width = 0; 4365 int charLen; 4366 for (i=b+1; i<p+1; i+=charLen) { 4367 width += measurePropChar(textD, 4368 getCharacter32(textD, buf, i, &charLen), colNum, 4369 i+styleBufOffset); 4370 colNum++; 4371 } 4372 } else 4373 colNum = BufCountDispChars(buf, b+1, p+1); 4374 foundBreak = True; 4375 break; 4376 } 4377 } 4378 if (!foundBreak) { /* no whitespace, just break at margin */ 4379 newLineStart = max(p, lineStart+1); 4380 colNum = BufCharWidth((char)c, colNum, tabDist, nullSubsChar); 4381 if (countPixels) 4382 width = measurePropChar(textD, c, colNum, p+styleBufOffset); 4383 } 4384 if (p >= maxPos) { 4385 *retPos = maxPos; 4386 *retLines = maxPos < newLineStart ? nLines : nLines + 1; 4387 *retLineStart = maxPos < newLineStart ? lineStart : 4388 newLineStart; 4389 *retLineEnd = maxPos; 4390 return; 4391 } 4392 nLines++; 4393 if (nLines >= maxLines) { 4394 *retPos = foundBreak ? b + 1 : max(p, lineStart+1); 4395 *retLines = nLines; 4396 *retLineStart = lineStart; 4397 *retLineEnd = foundBreak ? b : p; 4398 return; 4399 } 4400 lineStart = newLineStart; 4401 } 4402 } 4403 4404 /* reached end of buffer before reaching pos or line target */ 4405 *retPos = buf->length; 4406 *retLines = nLines; 4407 *retLineStart = lineStart; 4408 *retLineEnd = buf->length; 4409 return; 4410 } 4411 4412 /* 4413 ** Measure the width in pixels of a character "c" at a particular column 4414 ** "colNum" and buffer position "pos". This is for measuring characters in 4415 ** proportional or mixed-width highlighting fonts. 4416 ** 4417 ** A note about proportional and mixed-width fonts: the mixed width and 4418 ** proportional font code in nedit does not get much use in general editing, 4419 ** because nedit doesn't allow per-language-mode fonts, and editing programs 4420 ** in a proportional font is usually a bad idea, so very few users would 4421 ** choose a proportional font as a default. There are still probably mixed- 4422 ** width syntax highlighting cases where things don't redraw properly for 4423 ** insertion/deletion, though static display and wrapping and resizing 4424 ** should now be solid because they are now used for online help display. 4425 */ 4426 static int measurePropChar(const textDisp* textD, FcChar32 c, 4427 int colNum, int pos) 4428 { 4429 int style; 4430 textBuffer *styleBuf = textD->styleBuffer; 4431 4432 NFont *font = NULL; 4433 if (styleBuf) { 4434 style = (unsigned char)BufGetCharacter(styleBuf, pos); 4435 if (style == textD->unfinishedStyle) { 4436 /* encountered "unfinished" style, trigger parsing */ 4437 (textD->unfinishedHighlightCB)(textD, pos, textD->highlightCBArg); 4438 style = (unsigned char)BufGetCharacter(styleBuf, pos); 4439 } 4440 if (style & STYLE_LOOKUP_MASK) { 4441 font = textD->styleTable[(style & STYLE_LOOKUP_MASK) - ASCII_A].font; 4442 } 4443 } 4444 if(!font) { 4445 font = textD->font; 4446 } 4447 4448 int charLen; 4449 FcChar32 expChar[MAX_EXP_CHAR_LEN]; 4450 if(c < 128) { 4451 charLen = BufExpandCharacter4(c, colNum, expChar, 4452 textD->buffer->tabDist, textD->buffer->nullSubsChar); 4453 4454 if(font->minWidth == font->maxWidth) { 4455 return font->minWidth; 4456 } else { 4457 XGlyphInfo extents; 4458 XftTextExtents32(XtDisplay(textD->w), font->fonts->font, expChar, charLen, &extents); 4459 return extents.xOff; 4460 } 4461 } else { 4462 charLen = 1; 4463 expChar[0] = c; 4464 } 4465 4466 return charWidth4(textD, expChar, charLen, font); 4467 } 4468 4469 /* 4470 ** Finds both the end of the current line and the start of the next line. Why? 4471 ** In continuous wrap mode, if you need to know both, figuring out one from the 4472 ** other can be expensive or error prone. The problem comes when there's a 4473 ** trailing space or tab just before the end of the buffer. To translate an 4474 ** end of line value to or from the next lines start value, you need to know 4475 ** whether the trailing space or tab is being used as a line break or just a 4476 ** normal character, and to find that out would otherwise require counting all 4477 ** the way back to the beginning of the line. 4478 */ 4479 static void findLineEnd(textDisp *textD, int startPos, int startPosIsLineStart, 4480 int *lineEnd, int *nextLineStart) 4481 { 4482 int retLines, retLineStart; 4483 4484 /* if we're not wrapping use more efficient BufEndOfLine */ 4485 if (!textD->continuousWrap) { 4486 *lineEnd = BufEndOfLine(textD->buffer, startPos); 4487 *nextLineStart = min(textD->buffer->length, *lineEnd + 1); 4488 return; 4489 } 4490 4491 /* use the wrapped line counter routine to count forward one line */ 4492 wrappedLineCounter(textD, textD->buffer, startPos, textD->buffer->length, 4493 1, startPosIsLineStart, 0, nextLineStart, &retLines, 4494 &retLineStart, lineEnd, NULL); 4495 return; 4496 } 4497 4498 /* 4499 ** Line breaks in continuous wrap mode usually happen at newlines or 4500 ** whitespace. This line-terminating character is not included in line 4501 ** width measurements and has a special status as a non-visible character. 4502 ** However, lines with no whitespace are wrapped without the benefit of a 4503 ** line terminating character, and this distinction causes endless trouble 4504 ** with all of the text display code which was originally written without 4505 ** continuous wrap mode and always expects to wrap at a newline character. 4506 ** 4507 ** Given the position of the end of the line, as returned by TextDEndOfLine 4508 ** or BufEndOfLine, this returns true if there is a line terminating 4509 ** character, and false if there's not. On the last character in the 4510 ** buffer, this function can't tell for certain whether a trailing space was 4511 ** used as a wrap point, and just guesses that it wasn't. So if an exact 4512 ** accounting is necessary, don't use this function. 4513 */ 4514 static int wrapUsesCharacter(textDisp *textD, int lineEndPos) 4515 { 4516 char c; 4517 4518 if (!textD->continuousWrap || lineEndPos == textD->buffer->length) 4519 return True; 4520 4521 c = BufGetCharacter(textD->buffer, lineEndPos); 4522 return c == '\n' || ((c == '\t' || c == ' ') && 4523 lineEndPos + 1 != textD->buffer->length); 4524 } 4525 4526 /* 4527 ** Decide whether the user needs (or may need) a horizontal scroll bar, 4528 ** and manage or unmanage the scroll bar widget accordingly. The H. 4529 ** scroll bar is only hidden in continuous wrap mode when it's absolutely 4530 ** certain that the user will not need it: when wrapping is set 4531 ** to the window edge, or when the wrap margin is strictly less than 4532 ** the longest possible line. 4533 */ 4534 static void hideOrShowHScrollBar(textDisp *textD) 4535 { 4536 if (textD->continuousWrap && (textD->wrapMargin == 0 || textD->wrapMargin * 4537 FontDefault(textD->font)->max_advance_width < textD->width)) 4538 XtUnmanageChild(textD->hScrollBar); 4539 else 4540 XtManageChild(textD->hScrollBar); 4541 } 4542 4543 /* 4544 ** Return true if the selection "sel" is rectangular, and touches a 4545 ** buffer position withing "rangeStart" to "rangeEnd" 4546 */ 4547 static int rangeTouchesRectSel(selection *sel, int rangeStart, int rangeEnd) 4548 { 4549 return sel->selected && sel->rectangular && sel->end >= rangeStart && 4550 sel->start <= rangeEnd; 4551 } 4552 4553 /* 4554 ** Extend the range of a redraw request (from *start to *end) with additional 4555 ** redraw requests resulting from changes to the attached style buffer (which 4556 ** contains auxiliary information for coloring or styling text). 4557 */ 4558 static void extendRangeForStyleMods(textDisp *textD, int *start, int *end) 4559 { 4560 selection *sel = &textD->styleBuffer->primary; 4561 int extended = False; 4562 4563 /* The peculiar protocol used here is that modifications to the style 4564 buffer are marked by selecting them with the buffer's primary selection. 4565 The style buffer is usually modified in response to a modify callback on 4566 the text buffer BEFORE textDisp.c's modify callback, so that it can keep 4567 the style buffer in step with the text buffer. The style-update 4568 callback can't just call for a redraw, because textDisp hasn't processed 4569 the original text changes yet. Anyhow, to minimize redrawing and to 4570 avoid the complexity of scheduling redraws later, this simple protocol 4571 tells the text display's buffer modify callback to extend it's redraw 4572 range to show the text color/and font changes as well. */ 4573 if (sel->selected) { 4574 if (sel->start < *start) { 4575 *start = sel->start; 4576 extended = True; 4577 } 4578 if (sel->end > *end) { 4579 *end = sel->end; 4580 extended = True; 4581 } 4582 } 4583 4584 /* If the selection was extended due to a style change, and some of the 4585 fonts don't match in spacing, extend redraw area to end of line to 4586 redraw characters exposed by possible font size changes */ 4587 if (textD->fixedFontWidth == -1 && extended) 4588 *end = BufEndOfLine(textD->buffer, *end) + 1; 4589 } 4590 4591 /********************** Backlight Functions ******************************/ 4592 /* 4593 ** Allocate a read-only (shareable) colormap cell for a named color, from the 4594 ** the default colormap of the screen on which the widget (w) is displayed. If 4595 ** the colormap is full and there's no suitable substitute, print an error on 4596 ** stderr, and return the widget's background color as a backup. 4597 */ 4598 static XftColor allocBGColor(Widget w, char *colorName, int *ok) 4599 { 4600 int r,g,b; 4601 *ok = 1; 4602 XftColor color; 4603 color.pixel = AllocColor(w, colorName, &r, &g, &b); 4604 color.color.red = r; 4605 color.color.green = g; 4606 color.color.blue = b; 4607 color.color.alpha = 0xFFFF; 4608 return color; 4609 } 4610 4611 static XftColor* getRangesetColor(textDisp *textD, int ind, XftColor *bground) 4612 { 4613 textBuffer *buf; 4614 RangesetTable *tab; 4615 XftColor color; 4616 char *color_name; 4617 int valid; 4618 4619 if (ind > 0) { 4620 ind--; 4621 buf = textD->buffer; 4622 tab = buf->rangesetTable; 4623 4624 Rangeset *rangeset = NULL; 4625 valid = RangesetTableGetColorValid(tab, ind, &color, &rangeset); 4626 if (valid == 0) { 4627 color_name = RangesetTableGetColorName(tab, ind); 4628 if (color_name) 4629 color = allocBGColor(textD->w, color_name, &valid); 4630 rangeset = RangesetTableAssignColorPixel(tab, ind, color, valid); 4631 } 4632 if (valid > 0) { 4633 return RangesetGetColor(rangeset); 4634 } 4635 } 4636 return bground; 4637 } 4638 4639 /* 4640 ** Read the background color class specification string in str, allocating the 4641 ** necessary colors, and allocating and setting up the character->class_no and 4642 ** class_no->pixel map arrays, returned via *pp_bgClass and *pp_bgClassPixel 4643 ** respectively. 4644 ** Note: the allocation of class numbers could be more intelligent: there can 4645 ** never be more than 256 of these (one per character); but I don't think 4646 ** there'll be a pressing need. I suppose the scanning of the specification 4647 ** could be better too, but then, who cares! 4648 */ 4649 void TextDSetupBGClasses(Widget w, XmString str, XftColor **pp_bgClassPixel, 4650 unsigned char **pp_bgClass, XftColor bgPixelDefault) 4651 { 4652 unsigned char bgClass[256]; 4653 XftColor bgClassPixel[256]; 4654 int class_no = 0; 4655 char *semicol; 4656 char *s = (char *)str; 4657 size_t was_semicol; 4658 int lo, hi, dummy; 4659 char *pos; 4660 Boolean is_good = True; 4661 4662 NEditFree(*pp_bgClass); 4663 NEditFree(*pp_bgClassPixel); 4664 4665 *pp_bgClassPixel = NULL; 4666 *pp_bgClass = NULL; 4667 4668 if (!s) 4669 return; 4670 4671 /* default for all chars is class number zero, for standard background */ 4672 memset(bgClassPixel, 0, sizeof bgClassPixel); 4673 memset(bgClass, 0, sizeof bgClass); 4674 bgClassPixel[0] = bgPixelDefault; 4675 /* since class no == 0 in a "style" has no set bits in BACKLIGHT_MASK 4676 (see styleOfPos()), when drawString() is called for text with a 4677 backlight class no of zero, bgClassPixel[0] is never consulted, and 4678 the default background color is chosen. */ 4679 4680 /* The format of the class string s is: 4681 low[-high]{,low[-high]}:color{;low-high{,low[-high]}:color} 4682 eg 4683 32-255:#f0f0f0;1-31,127:red;128-159:orange;9-13:#e5e5e5 4684 where low and high represent a character range between ordinal 4685 ASCII values. Using strtol() allows automatic octal, dec and hex 4686 reading of low and high. The example format sets backgrounds as follows: 4687 char 1 - 8 colored red (control characters) 4688 char 9 - 13 colored #e5e5e5 (isspace() control characters) 4689 char 14 - 31 colored red (control characters) 4690 char 32 - 126 colored #f0f0f0 4691 char 127 colored red (delete character) 4692 char 128 - 159 colored orange ("shifted" control characters) 4693 char 160 - 255 colored #f0f0f0 4694 Notice that some of the later ranges overwrite the class values defined 4695 for earlier ones (eg the first clause, 32-255:#f0f0f0 sets the DEL 4696 character background color to #f0f0f0; it is then set to red by the 4697 clause 1-31,127:red). */ 4698 4699 while (s && class_no < 255) { 4700 class_no++; /* simple class alloc scheme */ 4701 was_semicol = 0; 4702 is_good = True; 4703 if ((semicol = (char *)strchr(s, ';'))) { 4704 *semicol = '\0'; /* null-terminate low[-high]:color clause */ 4705 was_semicol = 1; 4706 } 4707 4708 /* loop over ranges before the color spec, assigning the characters 4709 in the ranges to the current class number */ 4710 for (lo = hi = strtol(s, &pos, 0); 4711 is_good; 4712 lo = hi = strtol(pos + 1, &pos, 0)) { 4713 if (pos && *pos == '-') 4714 hi = strtol(pos + 1, &pos, 0); /* get end of range */ 4715 is_good = (pos && 0 <= lo && lo <= hi && hi <= 255); 4716 if (is_good) 4717 while (lo <= hi) 4718 bgClass[lo++] = (unsigned char)class_no; 4719 if (*pos != ',') 4720 break; 4721 } 4722 if ((is_good = (is_good && *pos == ':'))) { 4723 is_good = (*pos++ != '\0'); /* pos now points to color */ 4724 bgClassPixel[class_no] = allocBGColor(w, pos, &dummy); 4725 } 4726 if (!is_good) { 4727 /* complain? this class spec clause (in string s) was faulty */ 4728 } 4729 4730 /* end of loop iterator clauses */ 4731 if (was_semicol) 4732 *semicol = ';'; /* un-null-terminate low[-high]:color clause */ 4733 s = semicol + was_semicol; 4734 } 4735 4736 /* when we get here, we've set up our class table and class-to-pixel table 4737 in local variables: now put them into the "real thing" */ 4738 class_no++; /* bigger than all valid class_nos */ 4739 *pp_bgClass = (unsigned char *)NEditMalloc(256); 4740 *pp_bgClassPixel = (XftColor *)NEditMalloc(class_no * sizeof (XftColor)); 4741 if (!*pp_bgClass || !*pp_bgClassPixel) { 4742 NEditFree(*pp_bgClass); 4743 NEditFree(*pp_bgClassPixel); 4744 return; 4745 } 4746 memcpy(*pp_bgClass, bgClass, 256); 4747 memcpy(*pp_bgClassPixel, bgClassPixel, class_no * sizeof (XftColor)); 4748 } 4749 4750 4751 void TextDSetHighlightCursorLine(textDisp *textD, Boolean state) 4752 { 4753 textD->highlightCursorLine = state; 4754 } 4755 4756 void TextDSetIndentRainbow(textDisp *textD, Boolean indentRainbow) 4757 { 4758 textD->indentRainbow = indentRainbow; 4759 } 4760 4761 4762 #define ANSI_ESC_MAX_PARAM 16 4763 #define ANSI_ESC_MAX_PARAM_LEN 4 4764 4765 #define ANSI_ESC_RESET 0 4766 #define ANSI_ESC_BOLD 1 4767 #define ANSI_ESC_ITALIC 3 4768 #define ANSI_ESC_RESET_BOLD 22 4769 #define ANSI_ESC_RESET_ITALIC 23 4770 4771 #define ANSI_STYLE_SET(style, value) if(style < 0) style = value 4772 4773 /* Parses an ANSI Escape Sequence and sets the style in the ansiStyle object 4774 * Returns 1 if all possible settings are set 4775 */ 4776 static int parseEscapeSequence(textBuffer *buf, size_t pos, ansiStyle *style) 4777 { 4778 char c1 = BufGetCharacter(buf, pos); 4779 char c2 = BufGetCharacter(buf, pos+1); 4780 if(c1 != '\e' || c2 != '[') return 0; 4781 4782 int param[ANSI_ESC_MAX_PARAM]; 4783 int nparam = 0; 4784 4785 // parse the escape sequence into an array of integer parameters 4786 char paramDig[ANSI_ESC_MAX_PARAM_LEN]; 4787 int paramDigLen = 0; 4788 4789 size_t i = pos+2; 4790 while(nparam < ANSI_ESC_MAX_PARAM) { 4791 char c = BufGetCharacter(buf, i++); 4792 if(c >= '0' && c <= '9') { 4793 if(paramDigLen >= ANSI_ESC_MAX_PARAM_LEN) break; 4794 paramDig[paramDigLen++] = c - '0'; 4795 } else { 4796 int iParam = 0; 4797 int mul = 1; 4798 for(int j=paramDigLen-1;j>=0;j--) { 4799 iParam += paramDig[j] * mul; 4800 mul *= 10; 4801 } 4802 4803 param[nparam++] = iParam; 4804 4805 paramDigLen = 0; 4806 if(c != ';') break; 4807 } 4808 } 4809 4810 // evaluate parameter 4811 for(int k=0;k<nparam;k++) { 4812 int p = param[k]; 4813 4814 switch(p) { 4815 case ANSI_ESC_RESET: { 4816 ANSI_STYLE_SET(style->fg, 0); 4817 ANSI_STYLE_SET(style->bg, 0); 4818 ANSI_STYLE_SET(style->bold, 0); 4819 ANSI_STYLE_SET(style->italic, 0); 4820 k = nparam; // terminate loop 4821 break; 4822 } 4823 case ANSI_ESC_BOLD: { 4824 ANSI_STYLE_SET(style->bold, 1); 4825 break; 4826 } 4827 case ANSI_ESC_ITALIC: { 4828 ANSI_STYLE_SET(style->italic, 1); 4829 break; 4830 } 4831 case ANSI_ESC_RESET_BOLD: { 4832 ANSI_STYLE_SET(style->bold, 0); 4833 break; 4834 } 4835 case ANSI_ESC_RESET_ITALIC: { 4836 ANSI_STYLE_SET(style->bold, 0); 4837 break; 4838 } 4839 default: { 4840 if((p >= 30 && p <= 39) || (p >= 90 && p <= 97)) { 4841 ANSI_STYLE_SET(style->fg, p); 4842 } else if((p >= 40 && p <= 49) || (p >= 100 && p <= 107)) { 4843 ANSI_STYLE_SET(style->bg, p); 4844 } 4845 break; 4846 } 4847 } 4848 } 4849 4850 // check if all style options are set 4851 if(style->fg != -1 && style->bg != -1 && style->bold != -1 && style->italic) { 4852 return 1; 4853 } 4854 4855 4856 return 0; 4857 } 4858 4859 static void extendAnsiStyle(ansiStyle *style, ansiStyle *ext) 4860 { 4861 if(ext->fg >= 0) { 4862 style->fg = ext->fg; 4863 } 4864 if(ext->bg >= 0) { 4865 style->bg = ext->bg; 4866 } 4867 if(ext->bold >= 0) style->bold = ext->bold; 4868 if(ext->italic >= 0) style->italic = ext->italic; 4869 } 4870 4871 static void findActiveAnsiStyle(textDisp *textD, ssize_t pos, ansiStyle *style) 4872 { 4873 textBuffer *buf = textD->buffer; 4874 ssize_t prev_esc; // index of previous escape sequence 4875 size_t prev_esc_pos; // absolute position of previous esc 4876 BufEscPos2Index(buf, 0, 0, pos, &prev_esc, &prev_esc_pos); 4877 if(prev_esc < 0) { 4878 return; 4879 } 4880 4881 for(ssize_t i=prev_esc;i>=0;i--) { 4882 if(parseEscapeSequence(buf, prev_esc_pos, style)) { 4883 break; 4884 } 4885 prev_esc_pos -= buf->ansi_escpos[i]; // subtract offset to previous escape sequence 4886 } 4887 } 4888 4889 static void ansiFgToColorIndex(textDisp *textD, short fg, XftColor *color) 4890 { 4891 if(fg >= 30 && fg <= 37) { 4892 *color = textD->colorProfile->ansiColors[fg-30]; 4893 } else if(fg >= 90 && fg <= 97) { 4894 *color = textD->colorProfile->ansiColors[fg-90]; 4895 } 4896 } 4897 4898 static void ansiBgToColorIndex(textDisp *textD, short bg, XftColor *color) 4899 { 4900 if(bg >= 40 && bg <= 47) { 4901 *color = textD->colorProfile->ansiColors[bg-40]; 4902 } else if(bg >= 100 && bg <= 107) { 4903 *color = textD->colorProfile->ansiColors[bg-100]; 4904 } 4905 } 4906 4907 /* font list functions */ 4908 static void getFontMinMax(NFont *font, int *min, int *max) { 4909 XftFont *xftFont = FontDefault(font); 4910 int fontMin = xftFont->max_advance_width; 4911 int fontMax = 0; 4912 4913 for(int i=32;i<127;i++) { 4914 XGlyphInfo extents; 4915 FcChar8 c = i; 4916 XftTextExtents8(font->display, xftFont, &c, 1, &extents); 4917 if(extents.xOff < fontMin) { 4918 fontMin = extents.xOff; 4919 } 4920 if(extents.xOff > fontMax) { 4921 fontMax = extents.xOff; 4922 } 4923 } 4924 4925 *min = fontMin; 4926 *max = fontMax; 4927 } 4928 4929 NFont *FontCreate(Display *dp, FcPattern *pattern) 4930 { 4931 if(!pattern) { 4932 return NULL; 4933 } 4934 FcResult result; 4935 pattern = FcPatternDuplicate(pattern); 4936 FcPattern *match = XftFontMatch(dp, DefaultScreen(dp), pattern, &result); 4937 4938 double sz = 0; 4939 result = FcPatternGetDouble (pattern, FC_SIZE, 0, &sz); 4940 if(result != FcResultMatch) { 4941 FcPatternGetDouble (match, FC_SIZE, 0, &sz); 4942 } 4943 4944 XftFont *defaultFont = XftFontOpenPattern(dp, match); 4945 if(!defaultFont) { 4946 FcPatternDestroy(match); 4947 return NULL; 4948 } 4949 4950 NFont *font = NEditMalloc(sizeof(NFont)); 4951 font->display = dp; 4952 font->pattern = pattern; 4953 font->fail = NULL; 4954 font->size = sz; 4955 font->ref = 1; 4956 4957 NFontList *list = NEditMalloc(sizeof(NFontList)); 4958 list->font = defaultFont; 4959 list->next = NULL; 4960 font->fonts = list; 4961 4962 getFontMinMax(font, &font->minWidth, &font->maxWidth); 4963 4964 return font; 4965 } 4966 4967 NFont *FontFromName(Display *dp, const char *name) 4968 { 4969 FcPattern *pattern = FcNameParse((FcChar8*)name); 4970 if(!pattern) { 4971 return NULL; 4972 } 4973 NFont *font = FontCreate(dp, pattern); 4974 FcPatternDestroy(pattern); 4975 return font; 4976 } 4977 4978 XftFont *FontListAddFontForChar(NFont *f, FcChar32 c) 4979 { 4980 /* charset for char c */ 4981 FcCharSet *charset = FcCharSetCreate(); 4982 FcValue value; 4983 value.type = FcTypeCharSet; 4984 value.u.c = charset; 4985 FcCharSetAddChar(charset, c); 4986 if(!FcCharSetHasChar(charset, c)) { 4987 FcCharSetDestroy(charset); 4988 return f->fonts->font; 4989 } 4990 4991 /* font lookup based on the NFont pattern */ 4992 FcPattern *pattern = FcPatternDuplicate(f->pattern); 4993 FcPatternAdd(pattern, FC_CHARSET, value, 0); 4994 FcResult result; 4995 FcPattern *match = XftFontMatch ( 4996 f->display, DefaultScreen(f->display), pattern, &result); 4997 if(!match) { 4998 FcPatternDestroy(pattern); 4999 FontAddFail(f, charset); 5000 return f->fonts->font; 5001 } 5002 5003 XftFont *newFont = XftFontOpenPattern(f->display, match); 5004 if(!newFont || !FcCharSetHasChar(newFont->charset, c)) { 5005 FcPatternDestroy(pattern); 5006 FcPatternDestroy(match); 5007 if(newFont) { 5008 XftFontClose(f->display, newFont); 5009 } 5010 FontAddFail(f, charset); 5011 return f->fonts->font; 5012 } 5013 5014 FcCharSetDestroy(charset); 5015 5016 NFontList *newElm = NEditMalloc(sizeof(NFontList)); 5017 newElm->font = newFont; 5018 newElm->next = NULL; 5019 5020 NFontList *elm = f->fonts; 5021 NFontList *last = NULL; 5022 while(elm) { 5023 last = elm; 5024 elm = elm->next; 5025 } 5026 last->next = newElm; 5027 5028 5029 return newFont; 5030 } 5031 5032 XftFont *FindFont(NFont *f, FcChar32 c) 5033 { 5034 if(c < 128) { 5035 return f->fonts->font; 5036 } 5037 5038 /* make sure the char is not in the fail list, because we don't 5039 * want to retry font lookups */ 5040 NCharSetList *fail = f->fail; 5041 while(fail) { 5042 if(FcCharSetHasChar(fail->charset, c)) { 5043 return f->fonts->font; 5044 } 5045 fail = fail->next; 5046 5047 } 5048 5049 /* find a font that has this char */ 5050 NFontList *elm = f->fonts; 5051 while(elm) { 5052 if(FcCharSetHasChar(elm->font->charset, c)) { 5053 return elm->font; 5054 } 5055 elm = elm->next; 5056 } 5057 5058 /* open a new font for this char */ 5059 return FontListAddFontForChar(f, c); 5060 } 5061 5062 XftFont *FontDefault(NFont *f) { 5063 return f->fonts->font; 5064 } 5065 5066 void FontAddFail(NFont *f, FcCharSet *c) 5067 { 5068 NCharSetList *elm = f->fail; 5069 NCharSetList *last = elm; 5070 while(elm) { 5071 last = elm; 5072 elm = elm->next; 5073 } 5074 5075 NCharSetList *newElm = NEditMalloc(sizeof(NCharSetList)); 5076 newElm->charset = c; 5077 newElm->next = NULL; 5078 if(last) { 5079 last->next = newElm; 5080 } else { 5081 f->fail = newElm; 5082 } 5083 } 5084 5085 void FontDestroy(NFont *f) 5086 { 5087 NCharSetList *c = f->fail; 5088 NCharSetList *nc; 5089 while(c) { 5090 FcCharSetDestroy(c->charset); 5091 nc = c->next; 5092 NEditFree(c); 5093 c = nc; 5094 } 5095 5096 NFontList *l = f->fonts; 5097 NFontList *nl; 5098 while(l) { 5099 XftFontClose(f->display, l->font); 5100 nl = l->next; 5101 NEditFree(l); 5102 l = nl; 5103 } 5104 5105 FcPatternDestroy(f->pattern); 5106 NEditFree(f); 5107 } 5108 5109 NFont *FontRef(NFont *font) { 5110 font->ref++; 5111 return font; 5112 } 5113 5114 void FontUnref(NFont *font) { 5115 if(--font->ref == 0) { 5116 FontDestroy(font); 5117 } 5118 } 5119