Fix our use of $em_tab_dist after it was changed to 0 for 'turned of'.
[nedit.git] / source / selection.c
blob8c399193b814ec59a5e4bc36285de8376e11a595
1 static const char CVSID[] = "$Id: selection.c,v 1.34 2008/02/26 22:21:47 ajbj 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) ||
110 *endptr == '-' || *endptr == '+') {
111 break;
115 /* Get column */
116 if ( *endptr != '\0' ) {
117 tempNum = strtol( endptr, NULL, 10 );
118 if ( tempNum >= INT_MAX ) { *column = INT_MAX; }
119 else if ( tempNum < 0 ) { *column = 0; }
120 else { *column = tempNum; }
122 else { *column = -1; }
124 return *lineNum == -1 && *column == -1 ? -1 : 0;
127 void GotoLineNumber(WindowInfo *window)
129 char lineNumText[DF_MAX_PROMPT_LENGTH], *params[1];
130 int lineNum, column, response;
132 response = DialogF(DF_PROMPT, window->shell, 2, "Goto Line Number",
133 "Goto Line (and/or Column) Number:", lineNumText, "OK", "Cancel");
134 if (response == 2)
135 return;
137 if (StringToLineAndCol(lineNumText, &lineNum, &column) == -1) {
138 XBell(TheDisplay, 0);
139 return;
141 params[0] = lineNumText;
142 XtCallActionProc(window->lastFocus, "goto_line_number", NULL, params, 1);
145 void GotoSelectedLineNumber(WindowInfo *window, Time time)
147 XtGetSelectionValue(window->textArea, XA_PRIMARY, XA_STRING,
148 (XtSelectionCallbackProc)gotoCB, window, time);
151 void OpenSelectedFile(WindowInfo *window, Time time)
153 XtGetSelectionValue(window->textArea, XA_PRIMARY, XA_STRING,
154 (XtSelectionCallbackProc)fileCB, window, time);
158 ** Getting the current selection by making the request, and then blocking
159 ** (processing events) while waiting for a reply. On failure (timeout or
160 ** bad format) returns NULL, otherwise returns the contents of the selection.
162 char *GetAnySelection(WindowInfo *window)
164 static char waitingMarker[1] = "";
165 char *selText = waitingMarker;
166 XEvent nextEvent;
168 /* If the selection is in the window's own buffer get it from there,
169 but substitute null characters as if it were an external selection */
170 if (window->buffer->primary.selected) {
171 selText = BufGetSelectionText(window->buffer);
172 BufUnsubstituteNullChars(selText, window->buffer);
173 return selText;
176 /* Request the selection value to be delivered to getAnySelectionCB */
177 XtGetSelectionValue(window->textArea, XA_PRIMARY, XA_STRING,
178 (XtSelectionCallbackProc)getAnySelectionCB, &selText,
179 XtLastTimestampProcessed(XtDisplay(window->textArea)));
181 /* Wait for the value to appear */
182 while (selText == waitingMarker) {
183 XtAppNextEvent(XtWidgetToApplicationContext(window->textArea),
184 &nextEvent);
185 ServerDispatchEvent(&nextEvent);
187 return selText;
190 static void gotoCB(Widget widget, WindowInfo *window, Atom *sel,
191 Atom *type, char *value, int *length, int *format)
193 /* two integers and some space in between */
194 char lineText[(TYPE_INT_STR_SIZE(int) * 2) + 5];
195 int rc, lineNum, column, position, curCol;
197 /* skip if we can't get the selection data, or it's obviously not a number */
198 if (*type == XT_CONVERT_FAIL || value == NULL) {
199 XBell(TheDisplay, 0);
200 return;
202 if (((size_t) *length) > sizeof(lineText) - 1) {
203 XBell(TheDisplay, 0);
204 XtFree(value);
205 return;
207 /* should be of type text??? */
208 if (*format != 8) {
209 fprintf(stderr, "NEdit: Can't handle non 8-bit text\n");
210 XBell(TheDisplay, 0);
211 XtFree(value);
212 return;
214 strncpy(lineText, value, sizeof(lineText));
215 lineText[sizeof(lineText) - 1] = '\0';
217 rc = StringToLineAndCol(lineText, &lineNum, &column);
218 XtFree(value);
219 if (rc == -1) {
220 XBell(TheDisplay, 0);
221 return;
224 /* User specified column, but not line number */
225 if ( lineNum == -1 ) {
226 position = TextGetCursorPos(widget);
227 if (TextPosToLineAndCol(widget, position, &lineNum, &curCol) == False) {
228 XBell(TheDisplay, 0);
229 return;
232 /* User didn't specify a column */
233 else if ( column == -1 ) {
234 SelectNumberedLine(window, lineNum);
235 return;
238 position = TextLineAndColToPos(widget, lineNum, column );
239 if ( position == -1 ) {
240 XBell(TheDisplay, 0);
241 return;
243 TextSetCursorPos(widget, position);
246 static void fileCB(Widget widget, WindowInfo *window, Atom *sel,
247 Atom *type, char *value, int *length, int *format)
249 char nameText[MAXPATHLEN], includeName[MAXPATHLEN];
250 char filename[MAXPATHLEN], pathname[MAXPATHLEN];
251 char *inPtr, *outPtr;
252 #ifdef VMS
253 #ifndef __DECC
254 static char includeDir[] = "sys$library:";
255 #else
256 static char includeDir[] = "decc$library_include:";
257 #endif
258 #else
259 static char includeDir[] = "/usr/include/";
260 #endif /* VMS */
262 /* get the string, or skip if we can't get the selection data, or it's
263 obviously not a file name */
264 if (*type == XT_CONVERT_FAIL || value == NULL) {
265 XBell(TheDisplay, 0);
266 return;
268 if (*length > MAXPATHLEN || *length == 0) {
269 XBell(TheDisplay, 0);
270 XtFree(value);
271 return;
273 /* should be of type text??? */
274 if (*format != 8) {
275 fprintf(stderr, "NEdit: Can't handle non 8-bit text\n");
276 XBell(TheDisplay, 0);
277 XtFree(value);
278 return;
280 strncpy(nameText, value, *length);
281 XtFree(value);
282 nameText[*length] = '\0';
284 /* extract name from #include syntax */
285 if (sscanf(nameText, "#include \"%[^\"]\"", includeName) == 1)
286 strcpy(nameText, includeName);
287 else if (sscanf(nameText, "#include <%[^<>]>", includeName) == 1)
288 sprintf(nameText, "%s%s", includeDir, includeName);
290 /* strip whitespace from name */
291 for (inPtr=nameText, outPtr=nameText; *inPtr!='\0'; inPtr++)
292 if (*inPtr != ' ' && *inPtr != '\t' && *inPtr != '\n')
293 *outPtr++ = *inPtr;
294 *outPtr = '\0';
296 #ifdef VMS
297 /* If path name is relative, make it refer to current window's directory */
298 if ((strchr(nameText, ':') == NULL) && (strlen(nameText) > 1) &&
299 !((nameText[0] == '[') && (nameText[1] != '-') &&
300 (nameText[1] != '.'))) {
301 strcpy(filename, window->path);
302 strcat(filename, nameText);
303 strcpy(nameText, filename);
305 #else
306 /* Process ~ characters in name */
307 ExpandTilde(nameText);
309 /* If path name is relative, make it refer to current window's directory */
310 if (nameText[0] != '/') {
311 strcpy(filename, window->path);
312 strcat(filename, nameText);
313 strcpy(nameText, filename);
315 #endif
317 /* Expand wildcards in file name.
318 Some older systems don't have the glob subroutine for expanding file
319 names, in these cases, either don't expand names, or try to use the
320 Motif internal parsing routine _XmOSGetDirEntries, which is not
321 guranteed to be available, but in practice is there and does work. */
322 #if defined(DONT_HAVE_GLOB) || defined(VMS)
323 /* Open the file */
324 if (ParseFilename(nameText, filename, pathname) != 0) {
325 XBell(TheDisplay, 0);
326 return;
328 EditExistingFile(window, filename,
329 pathname, 0, NULL, False, NULL, GetPrefOpenInTab(), False);
330 #elif defined(USE_MOTIF_GLOB)
331 { char **nameList = NULL;
332 int i, nFiles = 0, maxFiles = 30;
334 if (ParseFilename(nameText, filename, pathname) != 0) {
335 XBell(TheDisplay, 0);
336 return;
338 _XmOSGetDirEntries(pathname, filename, XmFILE_ANY_TYPE, False, True,
339 &nameList, &nFiles, &maxFiles);
340 for (i=0; i<nFiles; i++) {
341 if (ParseFilename(nameList[i], filename, pathname) != 0) {
342 XBell(TheDisplay, 0);
344 else {
345 EditExistingFile(window, filename, pathname, 0,
346 NULL, False, NULL, GetPrefOpenInTab(), False);
349 for (i=0; i<nFiles; i++) {
350 XtFree(nameList[i]);
352 XtFree((char *)nameList);
354 #else
355 { glob_t globbuf;
356 int i;
358 glob(nameText, GLOB_NOCHECK, NULL, &globbuf);
359 for (i=0; i<(int)globbuf.gl_pathc; i++) {
360 if (ParseFilename(globbuf.gl_pathv[i], filename, pathname) != 0)
361 XBell(TheDisplay, 0);
362 else
363 EditExistingFile(GetPrefOpenInTab()? window : NULL,
364 filename, pathname, 0, NULL, False, NULL,
365 GetPrefOpenInTab(), False);
367 globfree(&globbuf);
369 #endif
370 CheckCloseDim();
373 static void getAnySelectionCB(Widget widget, char **result, Atom *sel,
374 Atom *type, char *value, int *length, int *format)
376 /* Confirm that the returned value is of the correct type */
377 if (*type != XA_STRING || *format != 8) {
378 XBell(TheDisplay, 0);
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;