Propogate colors to new windows on opening (744294)
[nedit.git] / source / selection.c
blobe69cb4f1f59ebb6e8b56b3173cda538da0230652
1 static const char CVSID[] = "$Id: selection.c,v 1.25 2003/05/09 17:43:48 edg 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. *
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 *******************************************************************************/
27 #ifdef HAVE_CONFIG_H
28 #include "../config.h"
29 #endif
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 "server.h"
39 #include "../util/DialogF.h"
40 #include "../util/fileUtils.h"
42 #include <stdlib.h>
43 #include <stdio.h>
44 #include <ctype.h>
45 #include <string.h>
46 #include <limits.h>
47 #ifdef VMS
48 #include "../util/VMSparam.h"
49 #else
50 #ifndef __MVS__
51 #include <sys/param.h>
52 #endif
53 #endif /*VMS*/
54 #if !defined(DONT_HAVE_GLOB) && !defined(USE_MOTIF_GLOB) && !defined(VMS)
55 #include <glob.h>
56 #endif
58 #include <Xm/Xm.h>
59 #include <X11/Xatom.h>
61 #ifdef HAVE_DEBUG_H
62 #include "../debug.h"
63 #endif
66 static void gotoCB(Widget widget, WindowInfo *window, Atom *sel,
67 Atom *type, char *value, int *length, int *format);
68 static void fileCB(Widget widget, WindowInfo *window, Atom *sel,
69 Atom *type, char *value, int *length, int *format);
70 static void getAnySelectionCB(Widget widget, char **result, Atom *sel,
71 Atom *type, char *value, int *length, int *format);
72 static void processMarkEvent(Widget w, XtPointer clientData, XEvent *event,
73 Boolean *continueDispatch, char *action, int extend);
74 static void markTimeoutProc(XtPointer clientData, XtIntervalId *id);
75 static void markKeyCB(Widget w, XtPointer clientData, XEvent *event,
76 Boolean *continueDispatch);
77 static void gotoMarkKeyCB(Widget w, XtPointer clientData, XEvent *event,
78 Boolean *continueDispatch);
79 static void gotoMarkExtendKeyCB(Widget w, XtPointer clientData, XEvent *event,
80 Boolean *continueDispatch);
81 static void maintainSelection(selection *sel, int pos, int nInserted,
82 int nDeleted);
83 static void maintainPosition(int *position, int modPos, int nInserted,
84 int nDeleted);
87 ** Extract the line and column number from the text string.
88 ** Set the line and/or column number to -1 if not specified, and return -1 if
89 ** both line and column numbers are not specified.
91 int StringToLineAndCol(const char *text, int *lineNum, int *column) {
92 char *endptr;
93 long tempNum;
94 int textLen;
96 /* Get line number */
97 tempNum = strtol( text, &endptr, 10 );
99 /* If user didn't specify a line number, set lineNum to -1 */
100 if ( endptr == text ) { *lineNum = -1; }
101 else if ( tempNum >= INT_MAX ) { *lineNum = INT_MAX; }
102 else if ( tempNum < 0 ) { *lineNum = 0; }
103 else { *lineNum = tempNum; }
105 /* Find the next digit */
106 for ( textLen = strlen( endptr ); textLen > 0; endptr++, textLen-- ) {
107 if (isdigit((unsigned char) *endptr ) || *endptr == '-' || *endptr == '+') {
108 break;
112 /* Get column */
113 if ( *endptr != '\0' ) {
114 tempNum = strtol( endptr, NULL, 10 );
115 if ( tempNum >= INT_MAX ) { *column = INT_MAX; }
116 else if ( tempNum < 0 ) { *column = 0; }
117 else { *column = tempNum; }
119 else { *column = -1; }
121 return *lineNum == -1 && *column == -1 ? -1 : 0;
124 void GotoLineNumber(WindowInfo *window)
126 char lineNumText[DF_MAX_PROMPT_LENGTH], *params[1];
127 int lineNum, column, response;
129 response = DialogF(DF_PROMPT, window->shell, 2, "Goto Line Number",
130 "Goto Line (and/or Column) Number:", lineNumText, "OK", "Cancel");
131 if (response == 2)
132 return;
134 if (StringToLineAndCol(lineNumText, &lineNum, &column) == -1) {
135 XBell(TheDisplay, 0);
136 return;
138 params[0] = lineNumText;
139 XtCallActionProc(window->lastFocus, "goto_line_number", NULL, params, 1);
142 void GotoSelectedLineNumber(WindowInfo *window, Time time)
144 XtGetSelectionValue(window->textArea, XA_PRIMARY, XA_STRING,
145 (XtSelectionCallbackProc)gotoCB, window, time);
148 void OpenSelectedFile(WindowInfo *window, Time time)
150 XtGetSelectionValue(window->textArea, XA_PRIMARY, XA_STRING,
151 (XtSelectionCallbackProc)fileCB, window, time);
155 ** Getting the current selection by making the request, and then blocking
156 ** (processing events) while waiting for a reply. On failure (timeout or
157 ** bad format) returns NULL, otherwise returns the contents of the selection.
159 char *GetAnySelection(WindowInfo *window)
161 static char waitingMarker[1] = "";
162 char *selText = waitingMarker;
163 XEvent nextEvent;
165 /* If the selection is in the window's own buffer get it from there,
166 but substitute null characters as if it were an external selection */
167 if (window->buffer->primary.selected) {
168 selText = BufGetSelectionText(window->buffer);
169 BufUnsubstituteNullChars(selText, window->buffer);
170 return selText;
173 /* Request the selection value to be delivered to getAnySelectionCB */
174 XtGetSelectionValue(window->textArea, XA_PRIMARY, XA_STRING,
175 (XtSelectionCallbackProc)getAnySelectionCB, &selText,
176 XtLastTimestampProcessed(XtDisplay(window->textArea)));
178 /* Wait for the value to appear */
179 while (selText == waitingMarker) {
180 XtAppNextEvent(XtWidgetToApplicationContext(window->textArea),
181 &nextEvent);
182 ServerDispatchEvent(&nextEvent);
184 return selText;
187 static void gotoCB(Widget widget, WindowInfo *window, Atom *sel,
188 Atom *type, char *value, int *length, int *format)
190 /* two integers and some space in between */
191 char lineText[(TYPE_INT_STR_SIZE(int) * 2) + 5];
192 int rc, lineNum, column, position, curCol;
194 /* skip if we can't get the selection data, or it's obviously not a number */
195 if (*type == XT_CONVERT_FAIL || value == NULL) {
196 XBell(TheDisplay, 0);
197 return;
199 if (((size_t) *length) > sizeof(lineText) - 1) {
200 XBell(TheDisplay, 0);
201 XtFree(value);
202 return;
204 /* should be of type text??? */
205 if (*format != 8) {
206 fprintf(stderr, "NEdit: Can't handle non 8-bit text\n");
207 XBell(TheDisplay, 0);
208 XtFree(value);
209 return;
211 strncpy(lineText, value, sizeof(lineText));
212 lineText[sizeof(lineText) - 1] = '\0';
214 rc = StringToLineAndCol(lineText, &lineNum, &column);
215 XtFree(value);
216 if (rc == -1) {
217 XBell(TheDisplay, 0);
218 return;
221 /* User specified column, but not line number */
222 if ( lineNum == -1 ) {
223 position = TextGetCursorPos(widget);
224 if (TextPosToLineAndCol(widget, position, &lineNum, &curCol) == False) {
225 XBell(TheDisplay, 0);
226 return;
229 /* User didn't specify a column */
230 else if ( column == -1 ) {
231 SelectNumberedLine(window, lineNum);
232 return;
235 position = TextLineAndColToPos(widget, lineNum, column );
236 if ( position == -1 ) {
237 XBell(TheDisplay, 0);
238 return;
240 TextSetCursorPos(widget, position);
243 static void fileCB(Widget widget, WindowInfo *window, Atom *sel,
244 Atom *type, char *value, int *length, int *format)
246 char nameText[MAXPATHLEN], includeName[MAXPATHLEN];
247 char filename[MAXPATHLEN], pathname[MAXPATHLEN];
248 char *inPtr, *outPtr;
249 #ifdef VMS
250 #ifndef __DECC
251 static char includeDir[] = "sys$library:";
252 #else
253 static char includeDir[] = "decc$library_include:";
254 #endif
255 #else
256 static char includeDir[] = "/usr/include/";
257 #endif /* VMS */
259 /* get the string, or skip if we can't get the selection data, or it's
260 obviously not a file name */
261 if (*type == XT_CONVERT_FAIL || value == NULL) {
262 XBell(TheDisplay, 0);
263 return;
265 if (*length > MAXPATHLEN || *length == 0) {
266 XBell(TheDisplay, 0);
267 XtFree(value);
268 return;
270 /* should be of type text??? */
271 if (*format != 8) {
272 fprintf(stderr, "NEdit: Can't handle non 8-bit text\n");
273 XBell(TheDisplay, 0);
274 XtFree(value);
275 return;
277 strncpy(nameText, value, *length);
278 XtFree(value);
279 nameText[*length] = '\0';
281 /* extract name from #include syntax */
282 if (sscanf(nameText, "#include \"%[^\"]\"", includeName) == 1)
283 strcpy(nameText, includeName);
284 else if (sscanf(nameText, "#include <%[^<>]>", includeName) == 1)
285 sprintf(nameText, "%s%s", includeDir, includeName);
287 /* strip whitespace from name */
288 for (inPtr=nameText, outPtr=nameText; *inPtr!='\0'; inPtr++)
289 if (*inPtr != ' ' && *inPtr != '\t' && *inPtr != '\n')
290 *outPtr++ = *inPtr;
291 *outPtr = '\0';
293 #ifdef VMS
294 /* If path name is relative, make it refer to current window's directory */
295 if ((strchr(nameText, ':') == NULL) && (strlen(nameText) > 1) &&
296 !((nameText[0] == '[') && (nameText[1] != '-') &&
297 (nameText[1] != '.'))) {
298 strcpy(filename, window->path);
299 strcat(filename, nameText);
300 strcpy(nameText, filename);
302 #else
303 /* Process ~ characters in name */
304 ExpandTilde(nameText);
306 /* If path name is relative, make it refer to current window's directory */
307 if (nameText[0] != '/') {
308 strcpy(filename, window->path);
309 strcat(filename, nameText);
310 strcpy(nameText, filename);
312 #endif
314 /* Expand wildcards in file name.
315 Some older systems don't have the glob subroutine for expanding file
316 names, in these cases, either don't expand names, or try to use the
317 Motif internal parsing routine _XmOSGetDirEntries, which is not
318 guranteed to be available, but in practice is there and does work. */
319 #if defined(DONT_HAVE_GLOB) || defined(VMS)
320 /* Open the file */
321 if (ParseFilename(nameText, filename, pathname) != 0) {
322 XBell(TheDisplay, 0);
323 return;
325 EditExistingFile(WindowList, filename, pathname, 0, NULL, False, NULL);
326 #elif defined(USE_MOTIF_GLOB)
327 { char **nameList = NULL;
328 int i, nFiles = 0, maxFiles = 30;
330 if (ParseFilename(nameText, filename, pathname) != 0) {
331 XBell(TheDisplay, 0);
332 return;
334 _XmOSGetDirEntries(pathname, filename, XmFILE_ANY_TYPE, False, True,
335 &nameList, &nFiles, &maxFiles);
336 for (i=0; i<nFiles; i++) {
337 if (ParseFilename(nameList[i], filename, pathname) != 0) {
338 XBell(TheDisplay, 0);
340 else {
341 EditExistingFile(WindowList, filename, pathname, 0, NULL,
342 False, NULL);
345 for (i=0; i<nFiles; i++) {
346 XtFree(nameList[i]);
348 XtFree((char *)nameList);
350 #else
351 { glob_t globbuf;
352 int i;
354 glob(nameText, GLOB_NOCHECK, NULL, &globbuf);
355 for (i=0; i<(int)globbuf.gl_pathc; i++) {
356 if (ParseFilename(globbuf.gl_pathv[i], filename, pathname) != 0)
357 XBell(TheDisplay, 0);
358 else
359 EditExistingFile(WindowList, filename, pathname, 0, NULL,
360 False, NULL);
362 globfree(&globbuf);
364 #endif
365 CheckCloseDim();
368 static void getAnySelectionCB(Widget widget, char **result, Atom *sel,
369 Atom *type, char *value, int *length, int *format)
371 /* Confirm that the returned value is of the correct type */
372 if (*type != XA_STRING || *format != 8) {
373 XBell(TheDisplay, 0);
374 if (value != NULL)
375 XtFree((char *)value);
376 *result = NULL;
377 return;
380 /* Append a null, and return the string */
381 *result = XtMalloc(*length + 1);
382 strncpy(*result, value, *length);
383 XtFree(value);
384 (*result)[*length] = '\0';
387 void SelectNumberedLine(WindowInfo *window, int lineNum)
389 int i, lineStart = 0, lineEnd;
391 /* count lines to find the start and end positions for the selection */
392 if (lineNum < 1)
393 lineNum = 1;
394 lineEnd = -1;
395 for (i=1; i<=lineNum && lineEnd<window->buffer->length; i++) {
396 lineStart = lineEnd + 1;
397 lineEnd = BufEndOfLine(window->buffer, lineStart);
400 /* highlight the line */
401 if (i>lineNum) {
402 /* Line was found */
403 if (lineEnd < window->buffer->length) {
404 BufSelect(window->buffer, lineStart, lineEnd+1);
405 } else {
406 /* Don't select past the end of the buffer ! */
407 BufSelect(window->buffer, lineStart, window->buffer->length);
409 } else {
410 /* Line was not found -> position the selection & cursor at the end
411 without making a real selection and beep */
412 lineStart = window->buffer->length;
413 BufSelect(window->buffer, lineStart, lineStart);
414 XBell(TheDisplay, 0);
416 MakeSelectionVisible(window, window->lastFocus);
417 TextSetCursorPos(window->lastFocus, lineStart);
420 void MarkDialog(WindowInfo *window)
422 char letterText[DF_MAX_PROMPT_LENGTH], *params[1];
423 int response;
425 response = DialogF(DF_PROMPT, window->shell, 2, "Mark",
426 "Enter a single letter label to use for recalling\n"
427 "the current selection and cursor position.\n\n"
428 "(To skip this dialog, use the accelerator key,\n"
429 "followed immediately by a letter key (a-z))", letterText, "OK",
430 "Cancel");
431 if (response == 2)
432 return;
433 if (strlen(letterText) != 1 || !isalpha((unsigned char)letterText[0])) {
434 XBell(TheDisplay, 0);
435 return;
437 params[0] = letterText;
438 XtCallActionProc(window->lastFocus, "mark", NULL, params, 1);
441 void GotoMarkDialog(WindowInfo *window, int extend)
443 char letterText[DF_MAX_PROMPT_LENGTH], *params[2];
444 int response;
446 response = DialogF(DF_PROMPT, window->shell, 2, "Goto Mark",
447 "Enter the single letter label used to mark\n"
448 "the selection and/or cursor position.\n\n"
449 "(To skip this dialog, use the accelerator\n"
450 "key, followed immediately by the letter)", letterText, "OK",
451 "Cancel");
452 if (response == 2)
453 return;
454 if (strlen(letterText) != 1 || !isalpha((unsigned char)letterText[0])) {
455 XBell(TheDisplay, 0);
456 return;
458 params[0] = letterText;
459 params[1] = "extend";
460 XtCallActionProc(window->lastFocus, "goto_mark", NULL, params,
461 extend ? 2 : 1);
465 ** Process a command to mark a selection. Expects the user to continue
466 ** the command by typing a label character. Handles both correct user
467 ** behavior (type a character a-z) or bad behavior (do nothing or type
468 ** something else).
470 void BeginMarkCommand(WindowInfo *window)
472 XtInsertEventHandler(window->lastFocus, KeyPressMask, False,
473 markKeyCB, window, XtListHead);
474 window->markTimeoutID = XtAppAddTimeOut(
475 XtWidgetToApplicationContext(window->shell), 4000,
476 markTimeoutProc, window->lastFocus);
480 ** Process a command to go to a marked selection. Expects the user to
481 ** continue the command by typing a label character. Handles both correct
482 ** user behavior (type a character a-z) or bad behavior (do nothing or type
483 ** something else).
485 void BeginGotoMarkCommand(WindowInfo *window, int extend)
487 XtInsertEventHandler(window->lastFocus, KeyPressMask, False,
488 extend ? gotoMarkExtendKeyCB : gotoMarkKeyCB, window, XtListHead);
489 window->markTimeoutID = XtAppAddTimeOut(
490 XtWidgetToApplicationContext(window->shell), 4000,
491 markTimeoutProc, window->lastFocus);
495 ** Xt timer procedure for removing event handler if user failed to type a
496 ** mark character withing the allowed time
498 static void markTimeoutProc(XtPointer clientData, XtIntervalId *id)
500 Widget w = (Widget)clientData;
501 WindowInfo *window = WidgetToWindow(w);
503 XtRemoveEventHandler(w, KeyPressMask, False, markKeyCB, window);
504 XtRemoveEventHandler(w, KeyPressMask, False, gotoMarkKeyCB, window);
505 XtRemoveEventHandler(w, KeyPressMask, False, gotoMarkExtendKeyCB, window);
506 window->markTimeoutID = 0;
510 ** Temporary event handlers for keys pressed after the mark or goto-mark
511 ** commands, If the key is valid, grab the key event and call the action
512 ** procedure to mark (or go to) the selection, otherwise, remove the handler
513 ** and give up.
515 static void processMarkEvent(Widget w, XtPointer clientData, XEvent *event,
516 Boolean *continueDispatch, char *action, int extend)
518 XKeyEvent *e = (XKeyEvent *)event;
519 WindowInfo *window = WidgetToWindow(w);
520 Modifiers modifiers;
521 KeySym keysym;
522 char *params[2], string[2];
524 XtTranslateKeycode(TheDisplay, e->keycode, e->state, &modifiers,
525 &keysym);
526 if ((keysym >= 'A' && keysym <= 'Z') || (keysym >= 'a' && keysym <= 'z')) {
527 string[0] = toupper(keysym);
528 string[1] = '\0';
529 params[0] = string;
530 params[1] = "extend";
531 XtCallActionProc(window->lastFocus, action, event, params,
532 extend ? 2 : 1);
533 *continueDispatch = False;
535 XtRemoveEventHandler(w, KeyPressMask, False, markKeyCB, window);
536 XtRemoveEventHandler(w, KeyPressMask, False, gotoMarkKeyCB, window);
537 XtRemoveEventHandler(w, KeyPressMask, False, gotoMarkExtendKeyCB, window);
538 XtRemoveTimeOut(window->markTimeoutID);
540 static void markKeyCB(Widget w, XtPointer clientData, XEvent *event,
541 Boolean *continueDispatch)
543 processMarkEvent(w, clientData, event, continueDispatch, "mark", False);
545 static void gotoMarkKeyCB(Widget w, XtPointer clientData, XEvent *event,
546 Boolean *continueDispatch)
548 processMarkEvent(w, clientData, event, continueDispatch, "goto_mark",False);
550 static void gotoMarkExtendKeyCB(Widget w, XtPointer clientData, XEvent *event,
551 Boolean *continueDispatch)
553 processMarkEvent(w, clientData, event, continueDispatch, "goto_mark", True);
556 void AddMark(WindowInfo *window, Widget widget, char label)
558 int index;
560 /* look for a matching mark to re-use, or advance
561 nMarks to create a new one */
562 label = toupper(label);
563 for (index=0; index<window->nMarks; index++) {
564 if (window->markTable[index].label == label)
565 break;
567 if (index >= MAX_MARKS) {
568 fprintf(stderr, "no more marks allowed\n"); /* shouldn't happen */
569 return;
571 if (index == window->nMarks)
572 window->nMarks++;
574 /* store the cursor location and selection position in the table */
575 window->markTable[index].label = label;
576 memcpy(&window->markTable[index].sel, &window->buffer->primary,
577 sizeof(selection));
578 window->markTable[index].cursorPos = TextGetCursorPos(widget);
581 void GotoMark(WindowInfo *window, Widget w, char label, int extendSel)
583 int index, oldStart, newStart, oldEnd, newEnd, cursorPos;
584 selection *sel, *oldSel;
586 /* look up the mark in the mark table */
587 label = toupper(label);
588 for (index=0; index<window->nMarks; index++) {
589 if (window->markTable[index].label == label)
590 break;
592 if (index == window->nMarks) {
593 XBell(TheDisplay, 0);
594 return;
597 /* reselect marked the selection, and move the cursor to the marked pos */
598 sel = &window->markTable[index].sel;
599 oldSel = &window->buffer->primary;
600 cursorPos = window->markTable[index].cursorPos;
601 if (extendSel) {
602 oldStart = oldSel->selected ? oldSel->start : TextGetCursorPos(w);
603 oldEnd = oldSel->selected ? oldSel->end : TextGetCursorPos(w);
604 newStart = sel->selected ? sel->start : cursorPos;
605 newEnd = sel->selected ? sel->end : cursorPos;
606 BufSelect(window->buffer, oldStart < newStart ? oldStart : newStart,
607 oldEnd > newEnd ? oldEnd : newEnd);
608 } else {
609 if (sel->selected) {
610 if (sel->rectangular)
611 BufRectSelect(window->buffer, sel->start, sel->end,
612 sel->rectStart, sel->rectEnd);
613 else
614 BufSelect(window->buffer, sel->start, sel->end);
615 } else
616 BufUnselect(window->buffer);
619 /* Move the window into a pleasing position relative to the selection
620 or cursor. MakeSelectionVisible is not great with multi-line
621 selections, and here we will sometimes give it one. And to set the
622 cursor position without first using the less pleasing capability
623 of the widget itself for bringing the cursor in to view, you have to
624 first turn it off, set the position, then turn it back on. */
625 XtVaSetValues(w, textNautoShowInsertPos, False, NULL);
626 TextSetCursorPos(w, cursorPos);
627 MakeSelectionVisible(window, window->lastFocus);
628 XtVaSetValues(w, textNautoShowInsertPos, True, NULL);
632 ** Keep the marks in the windows book-mark table up to date across
633 ** changes to the underlying buffer
635 void UpdateMarkTable(WindowInfo *window, int pos, int nInserted,
636 int nDeleted)
638 int i;
640 for (i=0; i<window->nMarks; i++) {
641 maintainSelection(&window->markTable[i].sel, pos, nInserted,
642 nDeleted);
643 maintainPosition(&window->markTable[i].cursorPos, pos, nInserted,
644 nDeleted);
649 ** Update a selection across buffer modifications specified by
650 ** "pos", "nDeleted", and "nInserted".
652 static void maintainSelection(selection *sel, int pos, int nInserted,
653 int nDeleted)
655 if (!sel->selected || pos > sel->end)
656 return;
657 maintainPosition(&sel->start, pos, nInserted, nDeleted);
658 maintainPosition(&sel->end, pos, nInserted, nDeleted);
659 if (sel->end <= sel->start)
660 sel->selected = False;
664 ** Update a position across buffer modifications specified by
665 ** "modPos", "nDeleted", and "nInserted".
667 static void maintainPosition(int *position, int modPos, int nInserted,
668 int nDeleted)
670 if (modPos > *position)
671 return;
672 if (modPos+nDeleted <= *position)
673 *position += nInserted - nDeleted;
674 else
675 *position = modPos;