UNIXworkcode

1 /******************************************************************************* 2 * * 3 * textDrag.c - Text Dragging routines for NEdit text widget * 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 * Dec. 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 "textDrag.h" 34 #include "textBuf.h" 35 #include "textDisp.h" 36 #include "textP.h" 37 #include "../util/nedit_malloc.h" 38 39 #include <limits.h> 40 41 #include <X11/Intrinsic.h> 42 #include <X11/IntrinsicP.h> 43 #include <Xm/Xm.h> 44 #include <Xm/XmP.h> 45 #if XmVersion >= 1002 46 #include <Xm/PrimitiveP.h> 47 #endif 48 49 #ifdef HAVE_DEBUG_H 50 #include "../debug.h" 51 #endif 52 53 static void trackModifyRange(int *rangeStart, int *modRangeEnd, 54 int *unmodRangeEnd, int modPos, int nInserted, int nDeleted); 55 static void findTextMargins(textBuffer *buf, int start, int end, int *leftMargin, 56 int *rightMargin); 57 static int findRelativeLineStart(textBuffer *buf, int referencePos, 58 int referenceLineNum, int newLineNum); 59 static int min3(int i1, int i2, int i3); 60 static int max3(int i1, int i2, int i3); 61 static int max(int i1, int i2); 62 63 /* 64 ** Start the process of dragging the current primary-selected text across 65 ** the window (move by dragging, as opposed to dragging to create the 66 ** selection) 67 */ 68 void BeginBlockDrag(TextWidget tw) 69 { 70 textDisp *textD = tw->text.textD; 71 textBuffer *buf = textD->buffer; 72 XftFont *font = FontDefault(textD->font); 73 int fontHeight = font->ascent + font->descent; 74 int fontWidth = font->max_advance_width; 75 selection *sel = &buf->primary; 76 int nLines, mousePos, lineStart; 77 int x, y, lineEnd; 78 79 char *text; 80 81 /* Save a copy of the whole text buffer as a backup, and for 82 deriving changes */ 83 tw->text.dragOrigBuf = BufCreate(); 84 BufSetTabDistance(tw->text.dragOrigBuf, buf->tabDist); 85 tw->text.dragOrigBuf->useTabs = buf->useTabs; 86 text = BufGetAll(buf); 87 BufSetAll(tw->text.dragOrigBuf, text); 88 NEditFree(text); 89 if (sel->rectangular) 90 BufRectSelect(tw->text.dragOrigBuf, sel->start, sel->end, sel->rectStart, 91 sel->rectEnd); 92 else 93 BufSelect(tw->text.dragOrigBuf, sel->start, sel->end); 94 95 /* Record the mouse pointer offsets from the top left corner of the 96 selection (the position where text will actually be inserted In dragging 97 non-rectangular selections) */ 98 if (sel->rectangular) { 99 tw->text.dragXOffset = tw->text.btnDownX + textD->horizOffset - 100 textD->left - sel->rectStart * fontWidth; 101 } else { 102 if (!TextDPositionToXY(textD, sel->start, &x, &y)) 103 x = BufCountDispChars(buf, TextDStartOfLine(textD, sel->start), 104 sel->start) * fontWidth + textD->left - 105 textD->horizOffset; 106 tw->text.dragXOffset = tw->text.btnDownX - x; 107 } 108 mousePos = TextDXYToPosition(textD, tw->text.btnDownX, tw->text.btnDownY); 109 nLines = BufCountLines(buf, sel->start, mousePos); 110 tw->text.dragYOffset = nLines * fontHeight + (((tw->text.btnDownY - 111 tw->text.marginHeight) % fontHeight) - fontHeight/2); 112 tw->text.dragNLines = BufCountLines(buf, sel->start, sel->end); 113 114 /* Record the current drag insert position and the information for 115 undoing the fictional insert of the selection in its new position */ 116 tw->text.dragInsertPos = sel->start; 117 tw->text.dragInserted = sel->end - sel->start; 118 if (sel->rectangular) { 119 textBuffer *testBuf = BufCreate(); 120 char *testText = BufGetRange(buf, sel->start, sel->end); 121 BufSetTabDistance(testBuf, buf->tabDist); 122 testBuf->useTabs = buf->useTabs; 123 BufSetAll(testBuf, testText); 124 NEditFree(testText); 125 BufRemoveRect(testBuf, 0, sel->end - sel->start, sel->rectStart, 126 sel->rectEnd); 127 tw->text.dragDeleted = testBuf->length; 128 BufFree(testBuf); 129 tw->text.dragRectStart = sel->rectStart; 130 } else { 131 tw->text.dragDeleted = 0; 132 tw->text.dragRectStart = 0; 133 } 134 tw->text.dragType = DRAG_MOVE; 135 tw->text.dragSourceDeletePos = sel->start; 136 tw->text.dragSourceInserted = tw->text.dragDeleted; 137 tw->text.dragSourceDeleted = tw->text.dragInserted; 138 139 /* For non-rectangular selections, fill in the rectangular information in 140 the selection for overlay mode drags which are done rectangularly */ 141 if (!sel->rectangular) { 142 lineStart = BufStartOfLine(buf, sel->start); 143 if (tw->text.dragNLines == 0) { 144 tw->text.dragOrigBuf->primary.rectStart = 145 BufCountDispChars(buf, lineStart, sel->start); 146 tw->text.dragOrigBuf->primary.rectEnd = 147 BufCountDispChars(buf, lineStart, sel->end); 148 } else { 149 lineEnd = BufGetCharacter(buf, sel->end - 1) == '\n' ? 150 sel->end - 1 : sel->end; 151 findTextMargins(buf, lineStart, lineEnd, 152 &tw->text.dragOrigBuf->primary.rectStart, 153 &tw->text.dragOrigBuf->primary.rectEnd); 154 } 155 } 156 157 /* Set the drag state to announce an ongoing block-drag */ 158 tw->text.dragState = PRIMARY_BLOCK_DRAG; 159 160 /* Call the callback announcing the start of a block drag */ 161 XtCallCallbacks((Widget)tw, textNdragStartCallback, (XtPointer)NULL); 162 } 163 164 /* 165 ** Reposition the primary-selected text that is being dragged as a block 166 ** for a new mouse position of (x, y) 167 */ 168 void BlockDragSelection(TextWidget tw, int x, int y, int dragType) 169 { 170 textDisp *textD = tw->text.textD; 171 textBuffer *buf = textD->buffer; 172 XftFont *font = FontDefault(textD->font); 173 int fontHeight = font->ascent + font->descent; 174 int fontWidth = font->max_advance_width; 175 textBuffer *origBuf = tw->text.dragOrigBuf; 176 int dragXOffset = tw->text.dragXOffset; 177 textBuffer *tempBuf; 178 selection *origSel = &origBuf->primary; 179 int rectangular = origSel->rectangular; 180 int overlay, oldDragType = tw->text.dragType; 181 int nLines = tw->text.dragNLines; 182 int insLineNum, insLineStart, insRectStart, insRectEnd, insStart; 183 char *repText, *text, *insText; 184 int modRangeStart = -1, tempModRangeEnd = -1, bufModRangeEnd = -1; 185 int referenceLine, referencePos, tempStart, tempEnd, origSelLen; 186 int insertInserted, insertDeleted, row, column; 187 int origSelLineStart, origSelLineEnd; 188 int sourceInserted, sourceDeleted, sourceDeletePos; 189 190 if (tw->text.dragState != PRIMARY_BLOCK_DRAG) 191 return; 192 193 /* The operation of block dragging is simple in theory, but not so simple 194 in practice. There is a backup buffer (tw->text.dragOrigBuf) which 195 holds a copy of the buffer as it existed before the drag. When the 196 user drags the mouse to a new location, this routine is called, and 197 a temporary buffer is created and loaded with the local part of the 198 buffer (from the backup) which might be changed by the drag. The 199 changes are all made to this temporary buffer, and the parts of this 200 buffer which then differ from the real (displayed) buffer are used to 201 replace those parts, thus one replace operation serves as both undo 202 and modify. This double-buffering of the operation prevents excessive 203 redrawing (though there is still plenty of needless redrawing due to 204 re-selection and rectangular operations). 205 206 The hard part is keeping track of the changes such that a single replace 207 operation will do everyting. This is done using a routine called 208 trackModifyRange which tracks expanding ranges of changes in the two 209 buffers in modRangeStart, tempModRangeEnd, and bufModRangeEnd. */ 210 211 /* Create a temporary buffer for accumulating changes which will 212 eventually be replaced in the real buffer. Load the buffer with the 213 range of characters which might be modified in this drag step 214 (this could be tighter, but hopefully it's not too slow) */ 215 tempBuf = BufCreate(); 216 tempBuf->tabDist = buf->tabDist; 217 tempBuf->useTabs = buf->useTabs; 218 tempStart = min3(tw->text.dragInsertPos, origSel->start, 219 BufCountBackwardNLines(buf, textD->firstChar, nLines+2)); 220 tempEnd = BufCountForwardNLines(buf, max3(tw->text.dragInsertPos, 221 origSel->start, textD->lastChar), nLines+2) + 222 origSel->end - origSel->start; 223 text = BufGetRange(origBuf, tempStart, tempEnd); 224 BufSetAll(tempBuf, text); 225 NEditFree(text); 226 227 /* If the drag type is USE_LAST, use the last dragType applied */ 228 if (dragType == USE_LAST) 229 dragType = tw->text.dragType; 230 overlay = dragType == DRAG_OVERLAY_MOVE || dragType == DRAG_OVERLAY_COPY; 231 232 /* Overlay mode uses rectangular selections whether or not the original 233 was rectangular. To use a plain selection as if it were rectangular, 234 the start and end positions need to be moved to the line boundaries 235 and trailing newlines must be excluded */ 236 origSelLineStart = BufStartOfLine(origBuf, origSel->start); 237 if (!rectangular && BufGetCharacter(origBuf, origSel->end - 1) == '\n') 238 origSelLineEnd = origSel->end - 1; 239 else 240 origSelLineEnd = BufEndOfLine(origBuf, origSel->end); 241 if (!rectangular && overlay && nLines != 0) 242 dragXOffset -= fontWidth * (origSel->rectStart - 243 (origSel->start - origSelLineStart)); 244 245 /* If the drag operation is of a different type than the last one, and the 246 operation is a move, expand the modified-range to include undoing the 247 text-removal at the site from which the text was dragged. */ 248 if (dragType != oldDragType && tw->text.dragSourceDeleted != 0) 249 trackModifyRange(&modRangeStart, &bufModRangeEnd, &tempModRangeEnd, 250 tw->text.dragSourceDeletePos, tw->text.dragSourceInserted, 251 tw->text.dragSourceDeleted); 252 253 /* Do, or re-do the original text removal at the site where a move began. 254 If this part has not changed from the last call, do it silently to 255 bring the temporary buffer in sync with the real (displayed) 256 buffer. If it's being re-done, track the changes to complete the 257 redo operation begun above */ 258 if (dragType == DRAG_MOVE || dragType == DRAG_OVERLAY_MOVE) { 259 if (rectangular || overlay) { 260 int prevLen = tempBuf->length; 261 origSelLen = origSelLineEnd - origSelLineStart; 262 if (overlay) 263 BufClearRect(tempBuf, origSelLineStart-tempStart, 264 origSelLineEnd-tempStart, origSel->rectStart, 265 origSel->rectEnd); 266 else 267 BufRemoveRect(tempBuf, origSelLineStart-tempStart, 268 origSelLineEnd-tempStart, origSel->rectStart, 269 origSel->rectEnd); 270 sourceDeletePos = origSelLineStart; 271 sourceInserted = origSelLen - prevLen + tempBuf->length; 272 sourceDeleted = origSelLen; 273 } else { 274 BufRemove(tempBuf, origSel->start - tempStart, 275 origSel->end - tempStart); 276 sourceDeletePos = origSel->start; 277 sourceInserted = 0; 278 sourceDeleted = origSel->end - origSel->start; 279 } 280 if (dragType != oldDragType) 281 trackModifyRange(&modRangeStart, &tempModRangeEnd, &bufModRangeEnd, 282 sourceDeletePos, sourceInserted, sourceDeleted); 283 } else { 284 sourceDeletePos = 0; 285 sourceInserted = 0; 286 sourceDeleted = 0; 287 } 288 289 /* Expand the modified-range to include undoing the insert from the last 290 call. */ 291 trackModifyRange(&modRangeStart, &bufModRangeEnd, &tempModRangeEnd, 292 tw->text.dragInsertPos, tw->text.dragInserted, tw->text.dragDeleted); 293 294 /* Find the line number and column of the insert position. Note that in 295 continuous wrap mode, these must be calculated as if the text were 296 not wrapped */ 297 TextDXYToUnconstrainedPosition(textD, max(0, x - dragXOffset), 298 max(0, y - (tw->text.dragYOffset % fontHeight)), &row, &column); 299 column = TextDOffsetWrappedColumn(textD, row, column); 300 row = TextDOffsetWrappedRow(textD, row); 301 insLineNum = row + textD->topLineNum - tw->text.dragYOffset / fontHeight; 302 303 /* find a common point of reference between the two buffers, from which 304 the insert position line number can be translated to a position */ 305 if (textD->firstChar > modRangeStart) { 306 referenceLine = textD->topLineNum - 307 BufCountLines(buf, modRangeStart, textD->firstChar); 308 referencePos = modRangeStart; 309 } else { 310 referencePos = textD->firstChar; 311 referenceLine = textD->topLineNum; 312 } 313 314 /* find the position associated with the start of the new line in the 315 temporary buffer */ 316 insLineStart = findRelativeLineStart(tempBuf, referencePos - tempStart, 317 referenceLine, insLineNum) + tempStart; 318 if (insLineStart - tempStart == tempBuf->length) 319 insLineStart = BufStartOfLine(tempBuf, insLineStart - tempStart) + 320 tempStart; 321 322 /* Find the actual insert position */ 323 if (rectangular || overlay) { 324 insStart = insLineStart; 325 insRectStart = column; 326 } else { /* note, this will fail with proportional fonts */ 327 insStart = BufCountForwardDispChars(tempBuf, insLineStart - tempStart, 328 column) + tempStart; 329 insRectStart = 0; 330 } 331 332 /* If the position is the same as last time, don't bother drawing (it 333 would be nice if this decision could be made earlier) */ 334 if (insStart == tw->text.dragInsertPos && 335 insRectStart == tw->text.dragRectStart && dragType == oldDragType) { 336 BufFree(tempBuf); 337 return; 338 } 339 340 /* Do the insert in the temporary buffer */ 341 if (rectangular || overlay) { 342 insText = BufGetTextInRect(origBuf, origSelLineStart, origSelLineEnd, 343 origSel->rectStart, origSel->rectEnd); 344 if (overlay) 345 BufOverlayRect(tempBuf, insStart - tempStart, insRectStart, 346 insRectStart + origSel->rectEnd - origSel->rectStart, 347 insText, &insertInserted, &insertDeleted); 348 else 349 BufInsertCol(tempBuf, insRectStart, insStart - tempStart, insText, 350 &insertInserted, &insertDeleted); 351 trackModifyRange(&modRangeStart, &tempModRangeEnd, &bufModRangeEnd, 352 insStart, insertInserted, insertDeleted); 353 NEditFree(insText); 354 } else { 355 insText = BufGetSelectionText(origBuf); 356 BufInsert(tempBuf, insStart - tempStart, insText); 357 trackModifyRange(&modRangeStart, &tempModRangeEnd, &bufModRangeEnd, 358 insStart, origSel->end - origSel->start, 0); 359 insertInserted = origSel->end - origSel->start; 360 insertDeleted = 0; 361 NEditFree(insText); 362 } 363 364 /* Make the changes in the real buffer */ 365 repText = BufGetRange(tempBuf, modRangeStart - tempStart, 366 tempModRangeEnd - tempStart); 367 BufFree(tempBuf); 368 TextDBlankCursor(textD); 369 BufReplace(buf, modRangeStart, bufModRangeEnd, repText); 370 NEditFree(repText); 371 372 /* Store the necessary information for undoing this step */ 373 tw->text.dragInsertPos = insStart; 374 tw->text.dragRectStart = insRectStart; 375 tw->text.dragInserted = insertInserted; 376 tw->text.dragDeleted = insertDeleted; 377 tw->text.dragSourceDeletePos = sourceDeletePos; 378 tw->text.dragSourceInserted = sourceInserted; 379 tw->text.dragSourceDeleted = sourceDeleted; 380 tw->text.dragType = dragType; 381 382 /* Reset the selection and cursor position */ 383 if (rectangular || overlay) { 384 insRectEnd = insRectStart + origSel->rectEnd - origSel->rectStart; 385 BufRectSelect(buf, insStart, insStart + insertInserted, insRectStart, 386 insRectEnd); 387 TextDSetInsertPosition(textD, BufCountForwardDispChars(buf, 388 BufCountForwardNLines(buf, insStart, tw->text.dragNLines), 389 insRectEnd)); 390 } else { 391 BufSelect(buf, insStart, insStart + origSel->end - origSel->start); 392 TextDSetInsertPosition(textD, insStart + origSel->end - origSel->start); 393 } 394 TextDUnblankCursor(textD); 395 XtCallCallbacks((Widget)tw, textNcursorMovementCallback, (XtPointer)NULL); 396 tw->text.emTabsBeforeCursor = 0; 397 } 398 399 /* 400 ** Complete a block text drag operation 401 */ 402 void FinishBlockDrag(TextWidget tw) 403 { 404 dragEndCBStruct endStruct; 405 int modRangeStart = -1, origModRangeEnd, bufModRangeEnd; 406 char *deletedText; 407 408 /* Find the changed region of the buffer, covering both the deletion 409 of the selected text at the drag start position, and insertion at 410 the drag destination */ 411 trackModifyRange(&modRangeStart, &bufModRangeEnd, &origModRangeEnd, 412 tw->text.dragSourceDeletePos, tw->text.dragSourceInserted, 413 tw->text.dragSourceDeleted); 414 trackModifyRange(&modRangeStart, &bufModRangeEnd, &origModRangeEnd, 415 tw->text.dragInsertPos, tw->text.dragInserted, 416 tw->text.dragDeleted); 417 418 /* Get the original (pre-modified) range of text from saved backup buffer */ 419 deletedText = BufGetRange(tw->text.dragOrigBuf, modRangeStart, 420 origModRangeEnd); 421 422 /* Free the backup buffer */ 423 BufFree(tw->text.dragOrigBuf); 424 425 /* Return to normal drag state */ 426 tw->text.dragState = NOT_CLICKED; 427 428 /* Call finish-drag calback */ 429 endStruct.startPos = modRangeStart; 430 endStruct.nCharsDeleted = origModRangeEnd - modRangeStart; 431 endStruct.nCharsInserted = bufModRangeEnd - modRangeStart; 432 endStruct.deletedText = deletedText; 433 XtCallCallbacks((Widget)tw, textNdragEndCallback, (XtPointer)&endStruct); 434 NEditFree(deletedText); 435 } 436 437 /* 438 ** Cancel a block drag operation 439 */ 440 void CancelBlockDrag(TextWidget tw) 441 { 442 textBuffer *buf = tw->text.textD->buffer; 443 textBuffer *origBuf = tw->text.dragOrigBuf; 444 selection *origSel = &origBuf->primary; 445 int modRangeStart = -1, origModRangeEnd, bufModRangeEnd; 446 char *repText; 447 dragEndCBStruct endStruct; 448 449 /* If the operation was a move, make the modify range reflect the 450 removal of the text from the starting position */ 451 if (tw->text.dragSourceDeleted != 0) 452 trackModifyRange(&modRangeStart, &bufModRangeEnd, &origModRangeEnd, 453 tw->text.dragSourceDeletePos, tw->text.dragSourceInserted, 454 tw->text.dragSourceDeleted); 455 456 /* Include the insert being undone from the last step in the modified 457 range. */ 458 trackModifyRange(&modRangeStart, &bufModRangeEnd, &origModRangeEnd, 459 tw->text.dragInsertPos, tw->text.dragInserted, tw->text.dragDeleted); 460 461 /* Make the changes in the buffer */ 462 repText = BufGetRange(origBuf, modRangeStart, origModRangeEnd); 463 BufReplace(buf, modRangeStart, bufModRangeEnd, repText); 464 NEditFree(repText); 465 466 /* Reset the selection and cursor position */ 467 if (origSel->rectangular) 468 BufRectSelect(buf, origSel->start, origSel->end, origSel->rectStart, 469 origSel->rectEnd); 470 else 471 BufSelect(buf, origSel->start, origSel->end); 472 TextDSetInsertPosition(tw->text.textD, buf->cursorPosHint); 473 XtCallCallbacks((Widget)tw, textNcursorMovementCallback, NULL); 474 tw->text.emTabsBeforeCursor = 0; 475 476 /* Free the backup buffer */ 477 BufFree(origBuf); 478 479 /* Indicate end of drag */ 480 tw->text.dragState = DRAG_CANCELED; 481 482 /* Call finish-drag calback */ 483 endStruct.startPos = 0; 484 endStruct.nCharsDeleted = 0; 485 endStruct.nCharsInserted = 0; 486 endStruct.deletedText = NULL; 487 XtCallCallbacks((Widget)tw, textNdragEndCallback, (XtPointer)&endStruct); 488 } 489 490 /* 491 ** Maintain boundaries of changed region between two buffers which 492 ** start out with identical contents, but diverge through insertion, 493 ** deletion, and replacement, such that the buffers can be reconciled 494 ** by replacing the changed region of either buffer with the changed 495 ** region of the other. 496 ** 497 ** rangeStart is the beginning of the modification region in the shared 498 ** coordinates of both buffers (which are identical up to rangeStart). 499 ** modRangeEnd is the end of the changed region for the buffer being 500 ** modified, unmodRangeEnd is the end of the region for the buffer NOT 501 ** being modified. A value of -1 in rangeStart indicates that there 502 ** have been no modifications so far. 503 */ 504 static void trackModifyRange(int *rangeStart, int *modRangeEnd, 505 int *unmodRangeEnd, int modPos, int nInserted, int nDeleted) 506 { 507 if (*rangeStart == -1) { 508 *rangeStart = modPos; 509 *modRangeEnd = modPos + nInserted; 510 *unmodRangeEnd = modPos + nDeleted; 511 } else { 512 if (modPos < *rangeStart) 513 *rangeStart = modPos; 514 if (modPos + nDeleted > *modRangeEnd) { 515 *unmodRangeEnd += modPos + nDeleted - *modRangeEnd; 516 *modRangeEnd = modPos + nInserted; 517 } else 518 *modRangeEnd += nInserted - nDeleted; 519 } 520 } 521 522 /* 523 ** Find the left and right margins of text between "start" and "end" in 524 ** buffer "buf". Note that "start is assumed to be at the start of a line. 525 */ 526 static void findTextMargins(textBuffer *buf, int start, int end, int *leftMargin, 527 int *rightMargin) 528 { 529 char c; 530 int pos, width = 0, maxWidth = 0, minWhite = INT_MAX, inWhite = True; 531 532 for (pos=start; pos<end; pos++) { 533 c = BufGetCharacter(buf, pos); 534 if (inWhite && c != ' ' && c != '\t') { 535 inWhite = False; 536 if (width < minWhite) 537 minWhite = width; 538 } 539 if (c == '\n') { 540 if (width > maxWidth) 541 maxWidth = width; 542 width = 0; 543 inWhite = True; 544 } else 545 width += BufCharWidth(c, width, buf->tabDist, buf->nullSubsChar); 546 } 547 if (width > maxWidth) 548 maxWidth = width; 549 *leftMargin = minWhite == INT_MAX ? 0 : minWhite; 550 *rightMargin = maxWidth; 551 } 552 553 /* 554 ** Find a text position in buffer "buf" by counting forward or backward 555 ** from a reference position with known line number 556 */ 557 static int findRelativeLineStart(textBuffer *buf, int referencePos, 558 int referenceLineNum, int newLineNum) 559 { 560 if (newLineNum < referenceLineNum) 561 return BufCountBackwardNLines(buf, referencePos, 562 referenceLineNum - newLineNum); 563 else if (newLineNum > referenceLineNum) 564 return BufCountForwardNLines(buf, referencePos, 565 newLineNum - referenceLineNum); 566 return BufStartOfLine(buf, referencePos); 567 } 568 569 static int min3(int i1, int i2, int i3) 570 { 571 if (i1 <= i2 && i1 <= i3) 572 return i1; 573 return i2 <= i3 ? i2 : i3; 574 } 575 576 static int max3(int i1, int i2, int i3) 577 { 578 if (i1 >= i2 && i1 >= i3) 579 return i1; 580 return i2 >= i3 ? i2 : i3; 581 } 582 583 static int max(int i1, int i2) 584 { 585 return i1 >= i2 ? i1 : i2; 586 } 587