UNIXworkcode

1 /******************************************************************************* 2 * * 3 * Copyright (C) 1999 Mark Edel * 4 * * 5 * This is free software; you can redistribute it and/or modify it under the * 6 * terms of the GNU General Public License as published by the Free Software * 7 * Foundation; either version 2 of the License, or (at your option) any later * 8 * version. In addition, you may distribute version of this program linked to * 9 * Motif or Open Motif. See README for details. * 10 * * 11 * This software is distributed in the hope that it will be useful, but WITHOUT * 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * 14 * for more details. * 15 * * 16 * You should have received a copy of the GNU General Public License along with * 17 * software; if not, write to the Free Software Foundation, Inc., 59 Temple * 18 * Place, Suite 330, Boston, MA 02111-1307 USA * 19 * * 20 * Nirvana Text Editor * 21 * May 10, 1991 * 22 * * 23 * Written by Mark Edel * 24 * * 25 *******************************************************************************/ 26 27 #ifdef HAVE_CONFIG_H 28 #include "../config.h" 29 #endif 30 31 #include "selection.h" 32 #include "textBuf.h" 33 #include "text.h" 34 #include "nedit.h" 35 #include "file.h" 36 #include "window.h" 37 #include "menu.h" 38 #include "preferences.h" 39 #include "server.h" 40 #include "../util/DialogF.h" 41 #include "../util/fileUtils.h" 42 #include "../util/nedit_malloc.h" 43 44 #include <stdlib.h> 45 #include <stdio.h> 46 #include <ctype.h> 47 #include <string.h> 48 #include <limits.h> 49 #include <sys/param.h> 50 51 #if !defined(DONT_HAVE_GLOB) && !defined(USE_MOTIF_GLOB) && !defined(VMS) 52 #include <glob.h> 53 #endif 54 55 #include <Xm/Xm.h> 56 #include <X11/Xatom.h> 57 58 #ifdef HAVE_DEBUG_H 59 #include "../debug.h" 60 #endif 61 62 63 static void gotoCB(Widget widget, XtPointer window, Atom *sel, 64 Atom *type, XtPointer value, unsigned long *length, int *format); 65 static void fileCB(Widget widget, XtPointer window, Atom *sel, 66 Atom *type, XtPointer value, unsigned long *length, int *format); 67 static void getAnySelectionCB(Widget widget, XtPointer result, Atom *sel, 68 Atom *type, XtPointer value, unsigned long *length, int *format); 69 static void processMarkEvent(Widget w, XtPointer clientData, XEvent *event, 70 Boolean *continueDispatch, char *action, int extend); 71 static void markTimeoutProc(XtPointer clientData, XtIntervalId *id); 72 static void markKeyCB(Widget w, XtPointer clientData, XEvent *event, 73 Boolean *continueDispatch); 74 static void gotoMarkKeyCB(Widget w, XtPointer clientData, XEvent *event, 75 Boolean *continueDispatch); 76 static void gotoMarkExtendKeyCB(Widget w, XtPointer clientData, XEvent *event, 77 Boolean *continueDispatch); 78 static void maintainSelection(selection *sel, int pos, int nInserted, 79 int nDeleted); 80 static void maintainPosition(int *position, int modPos, int nInserted, 81 int nDeleted); 82 83 /* 84 ** Extract the line and column number from the text string. 85 ** Set the line and/or column number to -1 if not specified, and return -1 if 86 ** both line and column numbers are not specified. 87 */ 88 int StringToLineAndCol(const char *text, int *lineNum, int *column) { 89 char *endptr; 90 long tempNum; 91 int textLen; 92 93 /* Get line number */ 94 tempNum = strtol( text, &endptr, 10 ); 95 96 /* If user didn't specify a line number, set lineNum to -1 */ 97 if ( endptr == text ) { *lineNum = -1; } 98 else if ( tempNum >= INT_MAX ) { *lineNum = INT_MAX; } 99 else if ( tempNum < 0 ) { *lineNum = 0; } 100 else { *lineNum = tempNum; } 101 102 /* Find the next digit */ 103 for ( textLen = strlen(endptr); textLen > 0; endptr++, textLen-- ) { 104 if (isdigit((unsigned char)*endptr) || 105 *endptr == '-' || *endptr == '+') { 106 break; 107 } 108 } 109 110 /* Get column */ 111 if ( *endptr != '\0' ) { 112 tempNum = strtol( endptr, NULL, 10 ); 113 if ( tempNum >= INT_MAX ) { *column = INT_MAX; } 114 else if ( tempNum < 0 ) { *column = 0; } 115 else { *column = tempNum; } 116 } 117 else { *column = -1; } 118 119 return *lineNum == -1 && *column == -1 ? -1 : 0; 120 } 121 122 void GotoLineNumber(WindowInfo *window) 123 { 124 char lineNumText[DF_MAX_PROMPT_LENGTH], *params[1]; 125 int lineNum, column, response; 126 127 response = DialogF(DF_PROMPT, window->shell, 2, "Goto Line Number", 128 "Goto Line (and/or Column) Number:", lineNumText, "OK", "Cancel"); 129 if (response == 2) 130 return; 131 132 if (StringToLineAndCol(lineNumText, &lineNum, &column) == -1) { 133 XBell(TheDisplay, 0); 134 return; 135 } 136 params[0] = lineNumText; 137 XtCallActionProc(window->lastFocus, "goto_line_number", NULL, params, 1); 138 } 139 140 void GotoSelectedLineNumber(WindowInfo *window, Time time) 141 { 142 XtGetSelectionValue(window->textArea, XA_PRIMARY, XA_STRING, 143 gotoCB, window, time); 144 } 145 146 void OpenSelectedFile(WindowInfo *window, Time time) 147 { 148 XtGetSelectionValue(window->textArea, XA_PRIMARY, XA_STRING, 149 fileCB, window, time); 150 } 151 152 /* 153 ** Getting the current selection by making the request, and then blocking 154 ** (processing events) while waiting for a reply. On failure (timeout or 155 ** bad format) returns NULL, otherwise returns the contents of the selection. 156 */ 157 char *GetAnySelection(WindowInfo *window) 158 { 159 static char waitingMarker[1] = ""; 160 char *selText = waitingMarker; 161 XEvent nextEvent; 162 163 /* If the selection is in the window's own buffer get it from there, 164 but substitute null characters as if it were an external selection */ 165 if (window->buffer->primary.selected) { 166 selText = BufGetSelectionText(window->buffer); 167 BufUnsubstituteNullChars(selText, window->buffer); 168 return selText; 169 } 170 171 /* Request the selection value to be delivered to getAnySelectionCB */ 172 XtGetSelectionValue(window->textArea, XA_PRIMARY, XA_STRING, 173 getAnySelectionCB, &selText, 174 XtLastTimestampProcessed(XtDisplay(window->textArea))); 175 176 /* Wait for the value to appear */ 177 while (selText == waitingMarker) { 178 XtAppNextEvent(XtWidgetToApplicationContext(window->textArea), 179 &nextEvent); 180 ServerDispatchEvent(&nextEvent); 181 } 182 return selText; 183 } 184 185 static void gotoCB(Widget widget, XtPointer wi, Atom *sel, 186 Atom *type, XtPointer v, unsigned long *length, int *format) 187 { 188 WindowInfo *window = wi; 189 char *value = v; 190 191 /* two integers and some space in between */ 192 char lineText[(TYPE_INT_STR_SIZE(int) * 2) + 5]; 193 int rc, lineNum, column, position, curCol; 194 195 /* skip if we can't get the selection data, or it's obviously not a number */ 196 if (*type == XT_CONVERT_FAIL || value == NULL) { 197 XBell(TheDisplay, 0); 198 return; 199 } 200 if (((size_t) *length) > sizeof(lineText) - 1) { 201 XBell(TheDisplay, 0); 202 NEditFree(value); 203 return; 204 } 205 /* should be of type text??? */ 206 if (*format != 8) { 207 fprintf(stderr, "XNEdit: Can''t handle non 8-bit text\n"); 208 XBell(TheDisplay, 0); 209 NEditFree(value); 210 return; 211 } 212 strncpy(lineText, value, sizeof(lineText)); 213 lineText[sizeof(lineText) - 1] = '\0'; 214 215 rc = StringToLineAndCol(lineText, &lineNum, &column); 216 NEditFree(value); 217 if (rc == -1) { 218 XBell(TheDisplay, 0); 219 return; 220 } 221 222 /* User specified column, but not line number */ 223 if ( lineNum == -1 ) { 224 position = TextGetCursorPos(widget); 225 if (TextPosToLineAndCol(widget, position, &lineNum, &curCol) == False) { 226 XBell(TheDisplay, 0); 227 return; 228 } 229 } 230 /* User didn't specify a column */ 231 else if ( column == -1 ) { 232 SelectNumberedLine(window, lineNum); 233 return; 234 } 235 236 position = TextLineAndColToPos(widget, lineNum, column ); 237 if ( position == -1 ) { 238 XBell(TheDisplay, 0); 239 return; 240 } 241 TextSetCursorPos(widget, position); 242 } 243 244 static void fileCB(Widget widget, XtPointer wi, Atom *sel, 245 Atom *type, XtPointer v, unsigned long *length, int *format) 246 { 247 WindowInfo *window = wi; 248 char *value = v; 249 250 char nameText[MAXPATHLEN], includeName[MAXPATHLEN]; 251 char filename[MAXPATHLEN], pathname[MAXPATHLEN]; 252 nameText[MAXPATHLEN-1] = 0; 253 char *inPtr, *outPtr; 254 static char includeDir[] = "/usr/include/"; 255 256 /* get the string, or skip if we can't get the selection data, or it's 257 obviously not a file name */ 258 if (*type == XT_CONVERT_FAIL || value == NULL) { 259 XBell(TheDisplay, 0); 260 return; 261 } 262 if (*length + 2 > MAXPATHLEN || *length == 0) { 263 XBell(TheDisplay, 0); 264 NEditFree(value); 265 return; 266 } 267 /* should be of type text??? */ 268 if (*format != 8) { 269 fprintf(stderr, "XNEdit: Can''t handle non 8-bit text\n"); 270 XBell(TheDisplay, 0); 271 NEditFree(value); 272 return; 273 } 274 strncpy(nameText, value, *length); 275 NEditFree(value); 276 nameText[*length] = '\0'; 277 278 /* extract name from #include syntax */ 279 if (sscanf(nameText, "#include \"%[^\"]\"", includeName) == 1) 280 strcpy(nameText, includeName); 281 else if (sscanf(nameText, "#include <%[^<>]>", includeName) == 1) 282 snprintf(nameText, MAXPATHLEN-1, "%s%s", includeDir, includeName); 283 284 /* strip whitespace from name */ 285 for (inPtr=nameText, outPtr=nameText; *inPtr!='\0'; inPtr++) 286 if (*inPtr != ' ' && *inPtr != '\t' && *inPtr != '\n') 287 *outPtr++ = *inPtr; 288 *outPtr = '\0'; 289 290 /* Process ~ characters in name */ 291 ExpandTilde(nameText); 292 293 /* If path name is relative, make it refer to current window's directory */ 294 if (nameText[0] != '/') { 295 strcpy(filename, window->path); 296 strcat(filename, nameText); 297 strcpy(nameText, filename); 298 } 299 300 /* Expand wildcards in file name. 301 Some older systems don't have the glob subroutine for expanding file 302 names, in these cases, either don't expand names, or try to use the 303 Motif internal parsing routine _XmOSGetDirEntries, which is not 304 guranteed to be available, but in practice is there and does work. */ 305 #if defined(DONT_HAVE_GLOB) || defined(VMS) 306 /* Open the file */ 307 if (ParseFilename(nameText, filename, pathname) != 0) { 308 XBell(TheDisplay, 0); 309 return; 310 } 311 EditExistingFile(window, filename, 312 pathname, 0, NULL, False, NULL, GetPrefOpenInTab(), False); 313 #elif defined(USE_MOTIF_GLOB) 314 { char **nameList = NULL; 315 int i, nFiles = 0, maxFiles = 30; 316 317 if (ParseFilename(nameText, filename, pathname) != 0) { 318 XBell(TheDisplay, 0); 319 return; 320 } 321 _XmOSGetDirEntries(pathname, filename, XmFILE_ANY_TYPE, False, True, 322 &nameList, &nFiles, &maxFiles); 323 for (i=0; i<nFiles; i++) { 324 if (ParseFilename(nameList[i], filename, pathname) != 0) { 325 XBell(TheDisplay, 0); 326 } 327 else { 328 EditExistingFile(window, filename, pathname, 0, 329 NULL, False, NULL, GetPrefOpenInTab(), False); 330 } 331 } 332 for (i=0; i<nFiles; i++) { 333 NEditFree(nameList[i]); 334 } 335 NEditFree(nameList); 336 } 337 #else 338 { glob_t globbuf; 339 int i; 340 341 glob(nameText, GLOB_NOCHECK, NULL, &globbuf); 342 for (i=0; i<(int)globbuf.gl_pathc; i++) { 343 if (ParseFilename(globbuf.gl_pathv[i], filename, pathname) != 0) 344 XBell(TheDisplay, 0); 345 else 346 EditExistingFile(GetPrefOpenInTab()? window : NULL, 347 filename, pathname, NULL, NULL, 0, NULL, False, NULL, 348 GetPrefOpenInTab(), False); 349 } 350 globfree(&globbuf); 351 } 352 #endif 353 CheckCloseDim(); 354 } 355 356 static void getAnySelectionCB(Widget widget, XtPointer voidresult, Atom *sel, 357 Atom *type, XtPointer voidvalue, unsigned long *length, int *format) 358 { 359 char **result = voidresult; 360 char *value = voidvalue; 361 362 /* Confirm that the returned value is of the correct type */ 363 if (*type != XA_STRING || *format != 8) { 364 XBell(TheDisplay, 0); 365 NEditFree(value); 366 *result = NULL; 367 return; 368 } 369 370 /* Append a null, and return the string */ 371 *result = (char*)NEditMalloc(*length + 1); 372 strncpy(*result, value, *length); 373 NEditFree(value); 374 (*result)[*length] = '\0'; 375 } 376 377 void SelectNumberedLine(WindowInfo *window, int lineNum) 378 { 379 int i, lineStart = 0, lineEnd; 380 381 /* count lines to find the start and end positions for the selection */ 382 if (lineNum < 1) 383 lineNum = 1; 384 lineEnd = -1; 385 for (i=1; i<=lineNum && lineEnd<window->buffer->length; i++) { 386 lineStart = lineEnd + 1; 387 lineEnd = BufEndOfLine(window->buffer, lineStart); 388 } 389 390 /* highlight the line */ 391 if (i>lineNum) { 392 /* Line was found */ 393 if (lineEnd < window->buffer->length) { 394 BufSelect(window->buffer, lineStart, lineEnd+1); 395 } else { 396 /* Don't select past the end of the buffer ! */ 397 BufSelect(window->buffer, lineStart, window->buffer->length); 398 } 399 } else { 400 /* Line was not found -> position the selection & cursor at the end 401 without making a real selection and beep */ 402 lineStart = window->buffer->length; 403 BufSelect(window->buffer, lineStart, lineStart); 404 XBell(TheDisplay, 0); 405 } 406 MakeSelectionVisible(window, window->lastFocus); 407 TextSetCursorPos(window->lastFocus, lineStart); 408 } 409 410 void MarkDialog(WindowInfo *window) 411 { 412 char letterText[DF_MAX_PROMPT_LENGTH], *params[1]; 413 int response; 414 415 response = DialogF(DF_PROMPT, window->shell, 2, "Mark", 416 "Enter a single letter label to use for recalling\n" 417 "the current selection and cursor position.\n\n" 418 "(To skip this dialog, use the accelerator key,\n" 419 "followed immediately by a letter key (a-z))", letterText, "OK", 420 "Cancel"); 421 if (response == 2) 422 return; 423 if (strlen(letterText) != 1 || !isalpha((unsigned char)letterText[0])) { 424 XBell(TheDisplay, 0); 425 return; 426 } 427 params[0] = letterText; 428 XtCallActionProc(window->lastFocus, "mark", NULL, params, 1); 429 } 430 431 void GotoMarkDialog(WindowInfo *window, int extend) 432 { 433 char letterText[DF_MAX_PROMPT_LENGTH], *params[2]; 434 int response; 435 436 response = DialogF(DF_PROMPT, window->shell, 2, "Goto Mark", 437 "Enter the single letter label used to mark\n" 438 "the selection and/or cursor position.\n\n" 439 "(To skip this dialog, use the accelerator\n" 440 "key, followed immediately by the letter)", letterText, "OK", 441 "Cancel"); 442 if (response == 2) 443 return; 444 if (strlen(letterText) != 1 || !isalpha((unsigned char)letterText[0])) { 445 XBell(TheDisplay, 0); 446 return; 447 } 448 params[0] = letterText; 449 params[1] = "extend"; 450 XtCallActionProc(window->lastFocus, "goto_mark", NULL, params, 451 extend ? 2 : 1); 452 } 453 454 /* 455 ** Process a command to mark a selection. Expects the user to continue 456 ** the command by typing a label character. Handles both correct user 457 ** behavior (type a character a-z) or bad behavior (do nothing or type 458 ** something else). 459 */ 460 void BeginMarkCommand(WindowInfo *window) 461 { 462 XtInsertEventHandler(window->lastFocus, KeyPressMask, False, 463 markKeyCB, window, XtListHead); 464 window->markTimeoutID = XtAppAddTimeOut( 465 XtWidgetToApplicationContext(window->shell), 4000, 466 markTimeoutProc, window->lastFocus); 467 } 468 469 /* 470 ** Process a command to go to a marked selection. Expects the user to 471 ** continue the command by typing a label character. Handles both correct 472 ** user behavior (type a character a-z) or bad behavior (do nothing or type 473 ** something else). 474 */ 475 void BeginGotoMarkCommand(WindowInfo *window, int extend) 476 { 477 XtInsertEventHandler(window->lastFocus, KeyPressMask, False, 478 extend ? gotoMarkExtendKeyCB : gotoMarkKeyCB, window, XtListHead); 479 window->markTimeoutID = XtAppAddTimeOut( 480 XtWidgetToApplicationContext(window->shell), 4000, 481 markTimeoutProc, window->lastFocus); 482 } 483 484 /* 485 ** Xt timer procedure for removing event handler if user failed to type a 486 ** mark character withing the allowed time 487 */ 488 static void markTimeoutProc(XtPointer clientData, XtIntervalId *id) 489 { 490 Widget w = (Widget)clientData; 491 WindowInfo *window = WidgetToWindow(w); 492 493 XtRemoveEventHandler(w, KeyPressMask, False, markKeyCB, window); 494 XtRemoveEventHandler(w, KeyPressMask, False, gotoMarkKeyCB, window); 495 XtRemoveEventHandler(w, KeyPressMask, False, gotoMarkExtendKeyCB, window); 496 window->markTimeoutID = 0; 497 } 498 499 /* 500 ** Temporary event handlers for keys pressed after the mark or goto-mark 501 ** commands, If the key is valid, grab the key event and call the action 502 ** procedure to mark (or go to) the selection, otherwise, remove the handler 503 ** and give up. 504 */ 505 static void processMarkEvent(Widget w, XtPointer clientData, XEvent *event, 506 Boolean *continueDispatch, char *action, int extend) 507 { 508 XKeyEvent *e = (XKeyEvent *)event; 509 WindowInfo *window = WidgetToWindow(w); 510 Modifiers modifiers; 511 KeySym keysym; 512 char *params[2], string[2]; 513 514 XtTranslateKeycode(TheDisplay, e->keycode, e->state, &modifiers, 515 &keysym); 516 if ((keysym >= 'A' && keysym <= 'Z') || (keysym >= 'a' && keysym <= 'z')) { 517 string[0] = toupper(keysym); 518 string[1] = '\0'; 519 params[0] = string; 520 params[1] = "extend"; 521 XtCallActionProc(window->lastFocus, action, event, params, 522 extend ? 2 : 1); 523 *continueDispatch = False; 524 } 525 XtRemoveEventHandler(w, KeyPressMask, False, markKeyCB, window); 526 XtRemoveEventHandler(w, KeyPressMask, False, gotoMarkKeyCB, window); 527 XtRemoveEventHandler(w, KeyPressMask, False, gotoMarkExtendKeyCB, window); 528 XtRemoveTimeOut(window->markTimeoutID); 529 } 530 static void markKeyCB(Widget w, XtPointer clientData, XEvent *event, 531 Boolean *continueDispatch) 532 { 533 processMarkEvent(w, clientData, event, continueDispatch, "mark", False); 534 } 535 static void gotoMarkKeyCB(Widget w, XtPointer clientData, XEvent *event, 536 Boolean *continueDispatch) 537 { 538 processMarkEvent(w, clientData, event, continueDispatch, "goto_mark",False); 539 } 540 static void gotoMarkExtendKeyCB(Widget w, XtPointer clientData, XEvent *event, 541 Boolean *continueDispatch) 542 { 543 processMarkEvent(w, clientData, event, continueDispatch, "goto_mark", True); 544 } 545 546 void AddMark(WindowInfo *window, Widget widget, char label) 547 { 548 int index; 549 550 /* look for a matching mark to re-use, or advance 551 nMarks to create a new one */ 552 label = toupper(label); 553 for (index=0; index<window->nMarks; index++) { 554 if (window->markTable[index].label == label) 555 break; 556 } 557 if (index >= MAX_MARKS) { 558 fprintf(stderr, "no more marks allowed\n"); /* shouldn't happen */ 559 return; 560 } 561 if (index == window->nMarks) 562 window->nMarks++; 563 564 /* store the cursor location and selection position in the table */ 565 window->markTable[index].label = label; 566 memcpy(&window->markTable[index].sel, &window->buffer->primary, 567 sizeof(selection)); 568 window->markTable[index].cursorPos = TextGetCursorPos(widget); 569 } 570 571 void GotoMark(WindowInfo *window, Widget w, char label, int extendSel) 572 { 573 int index, oldStart, newStart, oldEnd, newEnd, cursorPos; 574 selection *sel, *oldSel; 575 576 /* look up the mark in the mark table */ 577 label = toupper(label); 578 for (index=0; index<window->nMarks; index++) { 579 if (window->markTable[index].label == label) 580 break; 581 } 582 if (index == window->nMarks) { 583 XBell(TheDisplay, 0); 584 return; 585 } 586 587 /* reselect marked the selection, and move the cursor to the marked pos */ 588 sel = &window->markTable[index].sel; 589 oldSel = &window->buffer->primary; 590 cursorPos = window->markTable[index].cursorPos; 591 if (extendSel) { 592 oldStart = oldSel->selected ? oldSel->start : TextGetCursorPos(w); 593 oldEnd = oldSel->selected ? oldSel->end : TextGetCursorPos(w); 594 newStart = sel->selected ? sel->start : cursorPos; 595 newEnd = sel->selected ? sel->end : cursorPos; 596 BufSelect(window->buffer, oldStart < newStart ? oldStart : newStart, 597 oldEnd > newEnd ? oldEnd : newEnd); 598 } else { 599 if (sel->selected) { 600 if (sel->rectangular) 601 BufRectSelect(window->buffer, sel->start, sel->end, 602 sel->rectStart, sel->rectEnd); 603 else 604 BufSelect(window->buffer, sel->start, sel->end); 605 } else 606 BufUnselect(window->buffer); 607 } 608 609 /* Move the window into a pleasing position relative to the selection 610 or cursor. MakeSelectionVisible is not great with multi-line 611 selections, and here we will sometimes give it one. And to set the 612 cursor position without first using the less pleasing capability 613 of the widget itself for bringing the cursor in to view, you have to 614 first turn it off, set the position, then turn it back on. */ 615 XtVaSetValues(w, textNautoShowInsertPos, False, NULL); 616 TextSetCursorPos(w, cursorPos); 617 MakeSelectionVisible(window, window->lastFocus); 618 XtVaSetValues(w, textNautoShowInsertPos, True, NULL); 619 } 620 621 /* 622 ** Keep the marks in the windows book-mark table up to date across 623 ** changes to the underlying buffer 624 */ 625 void UpdateMarkTable(WindowInfo *window, int pos, int nInserted, 626 int nDeleted) 627 { 628 int i; 629 630 for (i=0; i<window->nMarks; i++) { 631 maintainSelection(&window->markTable[i].sel, pos, nInserted, 632 nDeleted); 633 maintainPosition(&window->markTable[i].cursorPos, pos, nInserted, 634 nDeleted); 635 } 636 } 637 638 /* 639 ** Update a selection across buffer modifications specified by 640 ** "pos", "nDeleted", and "nInserted". 641 */ 642 static void maintainSelection(selection *sel, int pos, int nInserted, 643 int nDeleted) 644 { 645 if (!sel->selected || pos > sel->end) 646 return; 647 maintainPosition(&sel->start, pos, nInserted, nDeleted); 648 maintainPosition(&sel->end, pos, nInserted, nDeleted); 649 if (sel->end <= sel->start) 650 sel->selected = False; 651 } 652 653 /* 654 ** Update a position across buffer modifications specified by 655 ** "modPos", "nDeleted", and "nInserted". 656 */ 657 static void maintainPosition(int *position, int modPos, int nInserted, 658 int nDeleted) 659 { 660 if (modPos > *position) 661 return; 662 if (modPos+nDeleted <= *position) 663 *position += nInserted - nDeleted; 664 else 665 *position = modPos; 666 } 667