1 static const char CVSID
[] = "$Id: selection.c,v 1.34 2008/02/26 22:21:47 ajbj 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
) ||
110 *endptr
== '-' || *endptr
== '+') {
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");
137 if (StringToLineAndCol(lineNumText
, &lineNum
, &column
) == -1) {
138 XBell(TheDisplay
, 0);
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
;
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
);
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
),
185 ServerDispatchEvent(&nextEvent
);
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);
202 if (((size_t) *length
) > sizeof(lineText
) - 1) {
203 XBell(TheDisplay
, 0);
207 /* should be of type text??? */
209 fprintf(stderr
, "NEdit: Can't handle non 8-bit text\n");
210 XBell(TheDisplay
, 0);
214 strncpy(lineText
, value
, sizeof(lineText
));
215 lineText
[sizeof(lineText
) - 1] = '\0';
217 rc
= StringToLineAndCol(lineText
, &lineNum
, &column
);
220 XBell(TheDisplay
, 0);
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);
232 /* User didn't specify a column */
233 else if ( column
== -1 ) {
234 SelectNumberedLine(window
, lineNum
);
238 position
= TextLineAndColToPos(widget
, lineNum
, column
);
239 if ( position
== -1 ) {
240 XBell(TheDisplay
, 0);
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
;
254 static char includeDir
[] = "sys$library:";
256 static char includeDir
[] = "decc$library_include:";
259 static char includeDir
[] = "/usr/include/";
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);
268 if (*length
> MAXPATHLEN
|| *length
== 0) {
269 XBell(TheDisplay
, 0);
273 /* should be of type text??? */
275 fprintf(stderr
, "NEdit: Can't handle non 8-bit text\n");
276 XBell(TheDisplay
, 0);
280 strncpy(nameText
, value
, *length
);
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')
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
);
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
);
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)
324 if (ParseFilename(nameText
, filename
, pathname
) != 0) {
325 XBell(TheDisplay
, 0);
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);
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);
345 EditExistingFile(window
, filename
, pathname
, 0,
346 NULL
, False
, NULL
, GetPrefOpenInTab(), False
);
349 for (i
=0; i
<nFiles
; i
++) {
352 XtFree((char *)nameList
);
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);
363 EditExistingFile(GetPrefOpenInTab()? window
: NULL
,
364 filename
, pathname
, 0, NULL
, False
, NULL
,
365 GetPrefOpenInTab(), False
);
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
);
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
;