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