1 static const char CVSID
[] = "$Id: selection.c,v 1.31 2004/08/01 10:06:11 yooden Exp $";
2 /*******************************************************************************
4 * Copyright (C) 1999 Mark Edel *
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. *
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 *
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 *
21 * Nirvana Text Editor *
24 * Written by Mark Edel *
26 *******************************************************************************/
29 #include "../config.h"
32 #include "selection.h"
39 #include "preferences.h"
41 #include "../util/DialogF.h"
42 #include "../util/fileUtils.h"
50 #include "../util/VMSparam.h"
53 #include <sys/param.h>
56 #if !defined(DONT_HAVE_GLOB) && !defined(USE_MOTIF_GLOB) && !defined(VMS)
61 #include <X11/Xatom.h>
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
,
85 static void maintainPosition(int *position
, int modPos
, int nInserted
,
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
) {
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
== '+') {
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");
136 if (StringToLineAndCol(lineNumText
, &lineNum
, &column
) == -1) {
137 XBell(TheDisplay
, 0);
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
;
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
);
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
),
184 ServerDispatchEvent(&nextEvent
);
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);
201 if (((size_t) *length
) > sizeof(lineText
) - 1) {
202 XBell(TheDisplay
, 0);
206 /* should be of type text??? */
208 fprintf(stderr
, "NEdit: Can't handle non 8-bit text\n");
209 XBell(TheDisplay
, 0);
213 strncpy(lineText
, value
, sizeof(lineText
));
214 lineText
[sizeof(lineText
) - 1] = '\0';
216 rc
= StringToLineAndCol(lineText
, &lineNum
, &column
);
219 XBell(TheDisplay
, 0);
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);
231 /* User didn't specify a column */
232 else if ( column
== -1 ) {
233 SelectNumberedLine(window
, lineNum
);
237 position
= TextLineAndColToPos(widget
, lineNum
, column
);
238 if ( position
== -1 ) {
239 XBell(TheDisplay
, 0);
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
;
253 static char includeDir
[] = "sys$library:";
255 static char includeDir
[] = "decc$library_include:";
258 static char includeDir
[] = "/usr/include/";
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);
267 if (*length
> MAXPATHLEN
|| *length
== 0) {
268 XBell(TheDisplay
, 0);
272 /* should be of type text??? */
274 fprintf(stderr
, "NEdit: Can't handle non 8-bit text\n");
275 XBell(TheDisplay
, 0);
279 strncpy(nameText
, value
, *length
);
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')
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
);
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
);
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)
323 if (ParseFilename(nameText
, filename
, pathname
) != 0) {
324 XBell(TheDisplay
, 0);
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);
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);
344 EditExistingFile(window
, filename
, pathname
, 0,
345 NULL
, False
, NULL
, GetPrefOpenInTab(), False
);
348 for (i
=0; i
<nFiles
; i
++) {
351 XtFree((char *)nameList
);
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);
362 EditExistingFile(GetPrefOpenInTab()? window
: NULL
,
363 filename
, pathname
, 0, NULL
, False
, NULL
,
364 GetPrefOpenInTab(), False
);
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);
379 XtFree((char *)value
);
384 /* Append a null, and return the string */
385 *result
= XtMalloc(*length
+ 1);
386 strncpy(*result
, value
, *length
);
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 */
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 */
407 if (lineEnd
< window
->buffer
->length
) {
408 BufSelect(window
->buffer
, lineStart
, lineEnd
+1);
410 /* Don't select past the end of the buffer ! */
411 BufSelect(window
->buffer
, lineStart
, window
->buffer
->length
);
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];
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",
437 if (strlen(letterText
) != 1 || !isalpha((unsigned char)letterText
[0])) {
438 XBell(TheDisplay
, 0);
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];
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",
458 if (strlen(letterText
) != 1 || !isalpha((unsigned char)letterText
[0])) {
459 XBell(TheDisplay
, 0);
462 params
[0] = letterText
;
463 params
[1] = "extend";
464 XtCallActionProc(window
->lastFocus
, "goto_mark", NULL
, params
,
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
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
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
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
);
526 char *params
[2], string
[2];
528 XtTranslateKeycode(TheDisplay
, e
->keycode
, e
->state
, &modifiers
,
530 if ((keysym
>= 'A' && keysym
<= 'Z') || (keysym
>= 'a' && keysym
<= 'z')) {
531 string
[0] = toupper(keysym
);
534 params
[1] = "extend";
535 XtCallActionProc(window
->lastFocus
, action
, event
, params
,
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
)
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
)
571 if (index
>= MAX_MARKS
) {
572 fprintf(stderr
, "no more marks allowed\n"); /* shouldn't happen */
575 if (index
== 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
,
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
)
596 if (index
== window
->nMarks
) {
597 XBell(TheDisplay
, 0);
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
;
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
);
614 if (sel
->rectangular
)
615 BufRectSelect(window
->buffer
, sel
->start
, sel
->end
,
616 sel
->rectStart
, sel
->rectEnd
);
618 BufSelect(window
->buffer
, sel
->start
, sel
->end
);
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
,
644 for (i
=0; i
<window
->nMarks
; i
++) {
645 maintainSelection(&window
->markTable
[i
].sel
, pos
, nInserted
,
647 maintainPosition(&window
->markTable
[i
].cursorPos
, pos
, nInserted
,
653 ** Update a selection across buffer modifications specified by
654 ** "pos", "nDeleted", and "nInserted".
656 static void maintainSelection(selection
*sel
, int pos
, int nInserted
,
659 if (!sel
->selected
|| pos
> sel
->end
)
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
,
674 if (modPos
> *position
)
676 if (modPos
+nDeleted
<= *position
)
677 *position
+= nInserted
- nDeleted
;