Xft support under OpenMotif 2.3.3 - I've been using this for quite a while on
[nedit.git] / source / file.c
blob26c87051f9c7e5cda576ae62a7a74ad821023889
1 static const char CVSID[] = "$Id: file.c,v 1.119 2008/11/05 09:09:44 lebert Exp $";
2 /*******************************************************************************
3 * *
4 * file.c -- Nirvana Editor file i/o *
5 * *
6 * Copyright (C) 1999 Mark Edel *
7 * *
8 * This is free software; you can redistribute it and/or modify it under the *
9 * terms of the GNU General Public License as published by the Free Software *
10 * Foundation; either version 2 of the License, or (at your option) any later *
11 * version. In addition, you may distribute versions of this program linked to *
12 * Motif or Open Motif. See README for details. *
13 * *
14 * This software is distributed in the hope that it will be useful, but WITHOUT *
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
16 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License *
17 * for more details. *
18 * *
19 * You should have received a copy of the GNU General Public License along with *
20 * software; if not, write to the Free Software Foundation, Inc., 59 Temple *
21 * Place, Suite 330, Boston, MA 02111-1307 USA *
22 * *
23 * Nirvana Text Editor *
24 * May 10, 1991 *
25 * *
26 * Written by Mark Edel *
27 * *
28 *******************************************************************************/
30 #ifdef HAVE_CONFIG_H
31 #include "../config.h"
32 #endif
34 #include "file.h"
35 #include "textBuf.h"
36 #include "text.h"
37 #include "window.h"
38 #include "preferences.h"
39 #include "undo.h"
40 #include "menu.h"
41 #include "tags.h"
42 #include "server.h"
43 #include "interpret.h"
44 #include "../util/misc.h"
45 #include "../util/DialogF.h"
46 #include "../util/fileUtils.h"
47 #include "../util/getfiles.h"
48 #include "../util/printUtils.h"
49 #include "../util/utils.h"
51 #include <errno.h>
52 #include <limits.h>
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #include <unistd.h>
58 #ifdef VMS
59 #include "../util/VMSparam.h"
60 #include <types.h>
61 #include <stat.h>
62 #include <unixio.h>
63 #else
64 #include <sys/types.h>
65 #include <sys/stat.h>
66 #ifndef __MVS__
67 #include <sys/param.h>
68 #endif
69 #include <fcntl.h>
70 #endif /*VMS*/
72 #include <Xm/Xm.h>
73 #include <Xm/ToggleB.h>
74 #include <Xm/FileSB.h>
75 #include <Xm/RowColumn.h>
76 #include <Xm/Form.h>
77 #include <Xm/Label.h>
79 #ifdef HAVE_DEBUG_H
80 #include "../debug.h"
81 #endif
83 /* Maximum frequency in miliseconds of checking for external modifications.
84 The periodic check is only performed on buffer modification, and the check
85 interval is only to prevent checking on every keystroke in case of a file
86 system which is slow to process stat requests (which I'm not sure exists) */
87 #define MOD_CHECK_INTERVAL 3000
89 static int doSave(WindowInfo *window);
90 static void safeClose(WindowInfo *window);
91 static int doOpen(WindowInfo *window, const char *name, const char *path,
92 int flags);
93 static void backupFileName(WindowInfo *window, char *name, size_t len);
94 static int writeBckVersion(WindowInfo *window);
95 static int bckError(WindowInfo *window, const char *errString, const char *file);
96 static int fileWasModifiedExternally(WindowInfo *window);
97 static const char *errorString(void);
98 static void addWrapNewlines(WindowInfo *window);
99 static void setFormatCB(Widget w, XtPointer clientData, XtPointer callData);
100 static void addWrapCB(Widget w, XtPointer clientData, XtPointer callData);
101 static int cmpWinAgainstFile(WindowInfo *window, const char *fileName);
102 static int min(int i1, int i2);
103 static void modifiedWindowDestroyedCB(Widget w, XtPointer clientData,
104 XtPointer callData);
105 static void forceShowLineNumbers(WindowInfo *window);
107 #ifdef VMS
108 void removeVersionNumber(char *fileName);
109 #endif /*VMS*/
111 WindowInfo *EditNewFile(WindowInfo *inWindow, char *geometry, int iconic,
112 const char *languageMode, const char *defaultPath)
114 char name[MAXPATHLEN];
115 WindowInfo *window;
116 size_t pathlen;
117 char *path;
119 /*... test for creatability? */
121 /* Find a (relatively) unique name for the new file */
122 UniqueUntitledName(name);
124 /* create new window/document */
125 if (inWindow)
126 window = CreateDocument(inWindow, name);
127 else
128 window = CreateWindow(name, geometry, iconic);
130 path = window->path;
131 strcpy(window->filename, name);
132 strcpy(path, (defaultPath && *defaultPath) ? defaultPath : GetCurrentDir());
133 pathlen = strlen(window->path);
134 #ifndef VMS
135 /* do we have a "/" at the end? if not, add one */
136 if (0 < pathlen && path[pathlen - 1] != '/' && pathlen < MAXPATHLEN - 1) {
137 strcpy(&path[pathlen], "/");
139 #else /* VMS */
140 /* A logical name should be followed by a colon so that the filename can
141 be added to it to make a full file specification; otherwise a directory
142 path had the form
143 device_or_logicalname:[dir.dir.dir]
144 this requires no separator before the file name.
146 if (0 < pathlen && strchr(":]", path[pathlen - 1]) == NULL) {
147 strcpy(&path[pathlen], ":"); /* could not find a separator at end */
149 /* TODO: is this enough for VMS? what of posix emulation? */
150 /* TODO: what about other platforms? */
151 #endif /* VMS */
152 SetWindowModified(window, FALSE);
153 CLEAR_ALL_LOCKS(window->lockReasons);
154 UpdateWindowReadOnly(window);
155 UpdateStatsLine(window);
156 UpdateWindowTitle(window);
157 RefreshTabState(window);
159 if (languageMode == NULL)
160 DetermineLanguageMode(window, True);
161 else
162 SetLanguageMode(window, FindLanguageMode(languageMode), True);
164 ShowTabBar(window, GetShowTabBar(window));
166 if (iconic && IsIconic(window))
167 RaiseDocument(window);
168 else
169 RaiseDocumentWindow(window);
171 SortTabBar(window);
172 return window;
176 ** Open an existing file specified by name and path. Use the window inWindow
177 ** unless inWindow is NULL or points to a window which is already in use
178 ** (displays a file other than Untitled, or is Untitled but modified). Flags
179 ** can be any of:
181 ** CREATE: If file is not found, (optionally) prompt the
182 ** user whether to create
183 ** SUPPRESS_CREATE_WARN When creating a file, don't ask the user
184 ** PREF_READ_ONLY Make the file read-only regardless
186 ** If languageMode is passed as NULL, it will be determined automatically
187 ** from the file extension or file contents.
189 ** If bgOpen is True, then the file will be open in background. This
190 ** works in association with the SetLanguageMode() function that has
191 ** the syntax highlighting deferred, in order to speed up the file-
192 ** opening operation when multiple files are being opened in succession.
194 WindowInfo *EditExistingFile(WindowInfo *inWindow, const char *name,
195 const char *path, int flags, char *geometry, int iconic,
196 const char *languageMode, int tabbed, int bgOpen)
198 WindowInfo *window;
199 char fullname[MAXPATHLEN];
201 /* first look to see if file is already displayed in a window */
202 window = FindWindowWithFile(name, path);
203 if (window != NULL) {
204 if (!bgOpen) {
205 if (iconic)
206 RaiseDocument(window);
207 else
208 RaiseDocumentWindow(window);
210 return window;
213 /* If an existing window isn't specified; or the window is already
214 in use (not Untitled or Untitled and modified), or is currently
215 busy running a macro; create the window */
216 if (inWindow == NULL) {
217 window = CreateWindow(name, geometry, iconic);
219 else if (inWindow->filenameSet || inWindow->fileChanged ||
220 inWindow->macroCmdData != NULL) {
221 if (tabbed) {
222 window = CreateDocument(inWindow, name);
224 else {
225 window = CreateWindow(name, geometry, iconic);
228 else {
229 /* open file in untitled document */
230 window = inWindow;
231 strcpy(window->path, path);
232 strcpy(window->filename, name);
233 if (!iconic && !bgOpen) {
234 RaiseDocumentWindow(window);
238 /* Open the file */
239 if (!doOpen(window, name, path, flags)) {
240 /* The user may have destroyed the window instead of closing the
241 warning dialog; don't close it twice */
242 safeClose(window);
244 return NULL;
246 forceShowLineNumbers(window);
248 /* Decide what language mode to use, trigger language specific actions */
249 if (languageMode == NULL)
250 DetermineLanguageMode(window, True);
251 else
252 SetLanguageMode(window, FindLanguageMode(languageMode), True);
254 /* update tab label and tooltip */
255 RefreshTabState(window);
256 SortTabBar(window);
257 ShowTabBar(window, GetShowTabBar(window));
259 if (!bgOpen)
260 RaiseDocument(window);
262 /* Bring the title bar and statistics line up to date, doOpen does
263 not necessarily set the window title or read-only status */
264 UpdateWindowTitle(window);
265 UpdateWindowReadOnly(window);
266 UpdateStatsLine(window);
268 /* Add the name to the convenience menu of previously opened files */
269 strcpy(fullname, path);
270 strcat(fullname, name);
271 if(GetPrefAlwaysCheckRelTagsSpecs())
272 AddRelTagsFile(GetPrefTagFile(), path, TAG);
273 AddToPrevOpenMenu(fullname);
275 return window;
278 void RevertToSaved(WindowInfo *window)
280 char name[MAXPATHLEN], path[MAXPATHLEN];
281 int i;
282 int insertPositions[MAX_PANES], topLines[MAX_PANES];
283 int horizOffsets[MAX_PANES];
284 int openFlags = 0;
285 Widget text;
287 /* Can't revert untitled windows */
288 if (!window->filenameSet)
290 DialogF(DF_WARN, window->shell, 1, "Error",
291 "Window '%s' was never saved, can't re-read", "OK",
292 window->filename);
293 return;
296 /* save insert & scroll positions of all of the panes to restore later */
297 for (i=0; i<=window->nPanes; i++) {
298 text = i==0 ? window->textArea : window->textPanes[i-1];
299 insertPositions[i] = TextGetCursorPos(text);
300 TextGetScroll(text, &topLines[i], &horizOffsets[i]);
303 /* re-read the file, update the window title if new file is different */
304 strcpy(name, window->filename);
305 strcpy(path, window->path);
306 RemoveBackupFile(window);
307 ClearUndoList(window);
308 openFlags |= IS_USER_LOCKED(window->lockReasons) ? PREF_READ_ONLY : 0;
309 if (!doOpen(window, name, path, openFlags)) {
310 /* This is a bit sketchy. The only error in doOpen that irreperably
311 damages the window is "too much binary data". It should be
312 pretty rare to be reverting something that was fine only to find
313 that now it has too much binary data. */
314 if (!window->fileMissing)
315 safeClose(window);
316 else {
317 /* Treat it like an externally modified file */
318 window->lastModTime=0;
319 window->fileMissing=FALSE;
321 return;
323 forceShowLineNumbers(window);
324 UpdateWindowTitle(window);
325 UpdateWindowReadOnly(window);
327 /* restore the insert and scroll positions of each pane */
328 for (i=0; i<=window->nPanes; i++) {
329 text = i==0 ? window->textArea : window->textPanes[i-1];
330 TextSetCursorPos(text, insertPositions[i]);
331 TextSetScroll(text, topLines[i], horizOffsets[i]);
336 ** Checks whether a window is still alive, and closes it only if so.
337 ** Intended to be used when the file could not be opened for some reason.
338 ** Normally the window is still alive, but the user may have closed the
339 ** window instead of the error dialog. In that case, we shouldn't close the
340 ** window a second time.
342 static void safeClose(WindowInfo *window)
344 WindowInfo* p = WindowList;
345 while(p) {
346 if (p == window) {
347 CloseWindow(window);
348 return;
350 p = p->next;
354 static int doOpen(WindowInfo *window, const char *name, const char *path,
355 int flags)
357 char fullname[MAXPATHLEN];
358 struct stat statbuf;
359 int fileLen, readLen;
360 char *fileString, *c;
361 FILE *fp = NULL;
362 int fd;
363 int resp;
365 /* initialize lock reasons */
366 CLEAR_ALL_LOCKS(window->lockReasons);
368 /* Update the window data structure */
369 strcpy(window->filename, name);
370 strcpy(window->path, path);
371 window->filenameSet = TRUE;
372 window->fileMissing = TRUE;
374 /* Get the full name of the file */
375 strcpy(fullname, path);
376 strcat(fullname, name);
378 /* Open the file */
379 #ifndef DONT_USE_ACCESS
380 /* The only advantage of this is if you use clearcase,
381 which messes up the mtime of files opened with r+,
382 even if they're never actually written.
383 To avoid requiring special builds for clearcase users,
384 this is now the default. */
386 if ((fp = fopen(fullname, "r")) != NULL) {
387 if(access(fullname, W_OK) != 0)
388 SET_PERM_LOCKED(window->lockReasons, TRUE);
389 #else
390 fp = fopen(fullname, "rb+");
391 if (fp == NULL) {
392 /* Error opening file or file is not writeable */
393 fp = fopen(fullname, "rb");
394 if (fp != NULL) {
395 /* File is read only */
396 SET_PERM_LOCKED(window->lockReasons, TRUE);
397 #endif
398 } else if (flags & CREATE && errno == ENOENT) {
399 /* Give option to create (or to exit if this is the only window) */
400 if (!(flags & SUPPRESS_CREATE_WARN)) {
401 /* on Solaris 2.6, and possibly other OSes, dialog won't
402 show if parent window is iconized. */
403 RaiseShellWindow(window->shell, False);
405 /* ask user for next action if file not found */
406 if (WindowList == window && window->next == NULL) {
407 resp = DialogF(DF_WARN, window->shell, 3, "New File",
408 "Can't open %s:\n%s", "New File", "Cancel",
409 "Exit NEdit", fullname, errorString());
411 else {
412 resp = DialogF(DF_WARN, window->shell, 2, "New File",
413 "Can't open %s:\n%s", "New File", "Cancel", fullname,
414 errorString());
417 if (resp == 2) {
418 return FALSE;
420 else if (resp == 3) {
421 exit(EXIT_SUCCESS);
425 /* Test if new file can be created */
426 if ((fd = creat(fullname, 0666)) == -1) {
427 DialogF(DF_ERR, window->shell, 1, "Error creating File",
428 "Can't create %s:\n%s", "OK", fullname, errorString());
429 return FALSE;
431 else {
432 #ifdef VMS
433 /* get correct version number and close before removing */
434 getname(fd, fullname);
435 #endif
436 close(fd);
437 remove(fullname);
440 SetWindowModified(window, FALSE);
441 if ((flags & PREF_READ_ONLY) != 0) {
442 SET_USER_LOCKED(window->lockReasons, TRUE);
444 UpdateWindowReadOnly(window);
445 return TRUE;
447 else {
448 /* A true error */
449 DialogF(DF_ERR, window->shell, 1, "Error opening File",
450 "Could not open %s%s:\n%s", "OK", path, name,
451 errorString());
452 return FALSE;
456 /* Get the length of the file, the protection mode, and the time of the
457 last modification to the file */
458 if (fstat(fileno(fp), &statbuf) != 0) {
459 fclose(fp);
460 window->filenameSet = FALSE; /* Temp. prevent check for changes. */
461 DialogF(DF_ERR, window->shell, 1, "Error opening File",
462 "Error opening %s", "OK", name);
463 window->filenameSet = TRUE;
464 return FALSE;
467 if (S_ISDIR(statbuf.st_mode)) {
468 fclose(fp);
469 window->filenameSet = FALSE; /* Temp. prevent check for changes. */
470 DialogF(DF_ERR, window->shell, 1, "Error opening File",
471 "Can't open directory %s", "OK", name);
472 window->filenameSet = TRUE;
473 return FALSE;
476 #ifdef S_ISBLK
477 if (S_ISBLK(statbuf.st_mode)) {
478 fclose(fp);
479 window->filenameSet = FALSE; /* Temp. prevent check for changes. */
480 DialogF(DF_ERR, window->shell, 1, "Error opening File",
481 "Can't open block device %s", "OK", name);
482 window->filenameSet = TRUE;
483 return FALSE;
485 #endif
486 fileLen = statbuf.st_size;
488 /* Allocate space for the whole contents of the file (unfortunately) */
489 fileString = (char *)malloc(fileLen+1); /* +1 = space for null */
490 if (fileString == NULL) {
491 fclose(fp);
492 window->filenameSet = FALSE; /* Temp. prevent check for changes. */
493 DialogF(DF_ERR, window->shell, 1, "Error while opening File",
494 "File is too large to edit", "OK");
495 window->filenameSet = TRUE;
496 return FALSE;
499 /* Read the file into fileString and terminate with a null */
500 readLen = fread(fileString, sizeof(char), fileLen, fp);
501 if (ferror(fp)) {
502 fclose(fp);
503 window->filenameSet = FALSE; /* Temp. prevent check for changes. */
504 DialogF(DF_ERR, window->shell, 1, "Error while opening File",
505 "Error reading %s:\n%s", "OK", name, errorString());
506 window->filenameSet = TRUE;
507 free(fileString);
508 return FALSE;
510 fileString[readLen] = 0;
512 /* Close the file */
513 if (fclose(fp) != 0) {
514 /* unlikely error */
515 DialogF(DF_WARN, window->shell, 1, "Error while opening File",
516 "Unable to close file", "OK");
517 /* we read it successfully, so continue */
520 /* Any errors that happen after this point leave the window in a
521 "broken" state, and thus RevertToSaved will abandon the window if
522 window->fileMissing is FALSE and doOpen fails. */
523 window->fileMode = statbuf.st_mode;
524 window->fileUid = statbuf.st_uid;
525 window->fileGid = statbuf.st_gid;
526 window->lastModTime = statbuf.st_mtime;
527 window->device = statbuf.st_dev;
528 window->inode = statbuf.st_ino;
529 window->fileMissing = FALSE;
531 /* Detect and convert DOS and Macintosh format files */
532 if (GetPrefForceOSConversion()) {
533 window->fileFormat = FormatOfFile(fileString);
534 if (window->fileFormat == DOS_FILE_FORMAT) {
535 ConvertFromDosFileString(fileString, &readLen, NULL);
536 } else if (window->fileFormat == MAC_FILE_FORMAT) {
537 ConvertFromMacFileString(fileString, readLen);
541 /* Display the file contents in the text widget */
542 window->ignoreModify = True;
543 BufSetAll(window->buffer, fileString);
544 window->ignoreModify = False;
546 /* Check that the length that the buffer thinks it has is the same
547 as what we gave it. If not, there were probably nuls in the file.
548 Substitute them with another character. If that is impossible, warn
549 the user, make the file read-only, and force a substitution */
550 if (window->buffer->length != readLen) {
551 if (!BufSubstituteNullChars(fileString, readLen, window->buffer)) {
552 resp = DialogF(DF_ERR, window->shell, 2, "Error while opening File",
553 "Too much binary data in file. You may view\n"
554 "it, but not modify or re-save its contents.", "View",
555 "Cancel");
556 if (resp == 2) {
557 return FALSE;
560 SET_TMBD_LOCKED(window->lockReasons, TRUE);
561 for (c = fileString; c < &fileString[readLen]; c++) {
562 if (*c == '\0') {
563 *c = (char) 0xfe;
566 window->buffer->nullSubsChar = (char) 0xfe;
568 window->ignoreModify = True;
569 BufSetAll(window->buffer, fileString);
570 window->ignoreModify = False;
573 /* Release the memory that holds fileString */
574 free(fileString);
576 /* Set window title and file changed flag */
577 if ((flags & PREF_READ_ONLY) != 0) {
578 SET_USER_LOCKED(window->lockReasons, TRUE);
580 if (IS_PERM_LOCKED(window->lockReasons)) {
581 window->fileChanged = FALSE;
582 UpdateWindowTitle(window);
583 } else {
584 SetWindowModified(window, FALSE);
585 if (IS_ANY_LOCKED(window->lockReasons)) {
586 UpdateWindowTitle(window);
589 UpdateWindowReadOnly(window);
591 return TRUE;
594 int IncludeFile(WindowInfo *window, const char *name)
596 struct stat statbuf;
597 int fileLen, readLen;
598 char *fileString;
599 FILE *fp = NULL;
601 /* Open the file */
602 fp = fopen(name, "rb");
603 if (fp == NULL)
605 DialogF(DF_ERR, window->shell, 1, "Error opening File",
606 "Could not open %s:\n%s", "OK", name, errorString());
607 return FALSE;
610 /* Get the length of the file */
611 if (fstat(fileno(fp), &statbuf) != 0)
613 DialogF(DF_ERR, window->shell, 1, "Error opening File",
614 "Error opening %s", "OK", name);
615 fclose(fp);
616 return FALSE;
619 if (S_ISDIR(statbuf.st_mode))
621 DialogF(DF_ERR, window->shell, 1, "Error opening File",
622 "Can't open directory %s", "OK", name);
623 fclose(fp);
624 return FALSE;
626 fileLen = statbuf.st_size;
628 /* allocate space for the whole contents of the file */
629 fileString = (char *)malloc(fileLen+1); /* +1 = space for null */
630 if (fileString == NULL)
632 DialogF(DF_ERR, window->shell, 1, "Error opening File",
633 "File is too large to include", "OK");
634 fclose(fp);
635 return FALSE;
638 /* read the file into fileString and terminate with a null */
639 readLen = fread(fileString, sizeof(char), fileLen, fp);
640 if (ferror(fp))
642 DialogF(DF_ERR, window->shell, 1, "Error opening File",
643 "Error reading %s:\n%s", "OK", name, errorString());
644 fclose(fp);
645 free(fileString);
646 return FALSE;
648 fileString[readLen] = 0;
650 /* Detect and convert DOS and Macintosh format files */
651 switch (FormatOfFile(fileString)) {
652 case DOS_FILE_FORMAT:
653 ConvertFromDosFileString(fileString, &readLen, NULL);
654 break;
655 case MAC_FILE_FORMAT:
656 ConvertFromMacFileString(fileString, readLen);
657 break;
658 default:
659 /* Default is Unix, no conversion necessary. */
660 break;
663 /* If the file contained ascii nulls, re-map them */
664 if (!BufSubstituteNullChars(fileString, readLen, window->buffer))
666 DialogF(DF_ERR, window->shell, 1, "Error opening File",
667 "Too much binary data in file", "OK");
670 /* close the file */
671 if (fclose(fp) != 0)
673 /* unlikely error */
674 DialogF(DF_WARN, window->shell, 1, "Error opening File",
675 "Unable to close file", "OK");
676 /* we read it successfully, so continue */
679 /* insert the contents of the file in the selection or at the insert
680 position in the window if no selection exists */
681 if (window->buffer->primary.selected)
682 BufReplaceSelected(window->buffer, fileString);
683 else
684 BufInsert(window->buffer, TextGetCursorPos(window->lastFocus),
685 fileString);
687 /* release the memory that holds fileString */
688 free(fileString);
690 return TRUE;
694 ** Close all files and windows, leaving one untitled window
696 int CloseAllFilesAndWindows(void)
698 while (WindowList->next != NULL ||
699 WindowList->filenameSet || WindowList->fileChanged) {
701 * When we're exiting through a macro, the document running the
702 * macro does not disappear from the list, so we could get stuck
703 * in an endless loop if we try to close it. Therefore, we close
704 * other documents first. (Note that the document running the macro
705 * may get closed because it is in the same window as another
706 * document that gets closed, but it won't disappear; it becomes
707 * Untitled.)
709 if (WindowList == MacroRunWindow() && WindowList->next != NULL) {
710 if (!CloseAllDocumentInWindow(WindowList->next)) {
711 return False;
714 else {
715 if (!CloseAllDocumentInWindow(WindowList)) {
716 return False;
721 return TRUE;
724 int CloseFileAndWindow(WindowInfo *window, int preResponse)
726 int response, stat;
728 /* Make sure that the window is not in iconified state */
729 if (window->fileChanged)
730 RaiseDocumentWindow(window);
732 /* If the window is a normal & unmodified file or an empty new file,
733 or if the user wants to ignore external modifications then
734 just close it. Otherwise ask for confirmation first. */
735 if (!window->fileChanged &&
736 /* Normal File */
737 ((!window->fileMissing && window->lastModTime > 0) ||
738 /* New File*/
739 (window->fileMissing && window->lastModTime == 0) ||
740 /* File deleted/modified externally, ignored by user. */
741 !GetPrefWarnFileMods()))
743 CloseWindow(window);
744 /* up-to-date windows don't have outstanding backup files to close */
745 } else
747 if (preResponse == PROMPT_SBC_DIALOG_RESPONSE)
749 response = DialogF(DF_WARN, window->shell, 3, "Save File",
750 "Save %s before closing?", "Yes", "No", "Cancel", window->filename);
751 } else
753 response = preResponse;
756 if (response == YES_SBC_DIALOG_RESPONSE)
758 /* Save */
759 stat = SaveWindow(window);
760 if (stat)
762 CloseWindow(window);
763 } else
765 return FALSE;
767 } else if (response == NO_SBC_DIALOG_RESPONSE)
769 /* Don't Save */
770 RemoveBackupFile(window);
771 CloseWindow(window);
772 } else /* 3 == Cancel */
774 return FALSE;
777 return TRUE;
780 int SaveWindow(WindowInfo *window)
782 int stat;
784 /* Try to ensure our information is up-to-date */
785 CheckForChangesToFile(window);
787 /* Return success if the file is normal & unchanged or is a
788 read-only file. */
789 if ( (!window->fileChanged && !window->fileMissing &&
790 window->lastModTime > 0) ||
791 IS_ANY_LOCKED_IGNORING_PERM(window->lockReasons))
792 return TRUE;
793 /* Prompt for a filename if this is an Untitled window */
794 if (!window->filenameSet)
795 return SaveWindowAs(window, NULL, False);
797 /* Check for external modifications and warn the user */
798 if (GetPrefWarnFileMods() && fileWasModifiedExternally(window))
800 stat = DialogF(DF_WARN, window->shell, 2, "Save File",
801 "%s has been modified by another program.\n\n"
802 "Continuing this operation will overwrite any external\n"
803 "modifications to the file since it was opened in NEdit,\n"
804 "and your work or someone else's may potentially be lost.\n\n"
805 "To preserve the modified file, cancel this operation and\n"
806 "use Save As... to save this file under a different name,\n"
807 "or Revert to Saved to revert to the modified version.",
808 "Continue", "Cancel", window->filename);
809 if (stat == 2)
811 /* Cancel and mark file as externally modified */
812 window->lastModTime = 0;
813 window->fileMissing = FALSE;
814 return FALSE;
818 #ifdef VMS
819 RemoveBackupFile(window);
820 stat = doSave(window);
821 #else
822 if (writeBckVersion(window))
823 return FALSE;
824 stat = doSave(window);
825 if (stat)
826 RemoveBackupFile(window);
827 #endif /*VMS*/
828 return stat;
831 int SaveWindowAs(WindowInfo *window, const char *newName, int addWrap)
833 int response, retVal, fileFormat;
834 char fullname[MAXPATHLEN], filename[MAXPATHLEN], pathname[MAXPATHLEN];
835 WindowInfo *otherWindow;
837 /* Get the new name for the file */
838 if (newName == NULL) {
839 response = PromptForNewFile(window, "Save File As", fullname,
840 &fileFormat, &addWrap);
841 if (response != GFN_OK)
842 return FALSE;
843 window->fileFormat = fileFormat;
844 } else
846 strcpy(fullname, newName);
849 if (1 == NormalizePathname(fullname))
851 return False;
854 /* Add newlines if requested */
855 if (addWrap)
856 addWrapNewlines(window);
858 if (ParseFilename(fullname, filename, pathname) != 0) {
859 return FALSE;
862 /* If the requested file is this file, just save it and return */
863 if (!strcmp(window->filename, filename) &&
864 !strcmp(window->path, pathname)) {
865 if (writeBckVersion(window))
866 return FALSE;
867 return doSave(window);
870 /* If the file is open in another window, make user close it. Note that
871 it is possible for user to close the window by hand while the dialog
872 is still up, because the dialog is not application modal, so after
873 doing the dialog, check again whether the window still exists. */
874 otherWindow = FindWindowWithFile(filename, pathname);
875 if (otherWindow != NULL)
877 response = DialogF(DF_WARN, window->shell, 2, "File open",
878 "%s is open in another NEdit window", "Cancel",
879 "Close Other Window", filename);
881 if (response == 1)
883 return FALSE;
885 if (otherWindow == FindWindowWithFile(filename, pathname))
887 if (!CloseFileAndWindow(otherWindow, PROMPT_SBC_DIALOG_RESPONSE))
889 return FALSE;
894 /* Destroy the file closed property for the original file */
895 DeleteFileClosedProperty(window);
897 /* Change the name of the file and save it under the new name */
898 RemoveBackupFile(window);
899 strcpy(window->filename, filename);
900 strcpy(window->path, pathname);
901 window->fileMode = 0;
902 window->fileUid = 0;
903 window->fileGid = 0;
904 CLEAR_ALL_LOCKS(window->lockReasons);
905 retVal = doSave(window);
906 UpdateWindowReadOnly(window);
907 RefreshTabState(window);
909 /* Add the name to the convenience menu of previously opened files */
910 AddToPrevOpenMenu(fullname);
912 /* If name has changed, language mode may have changed as well, unless
913 it's an Untitled window for which the user already set a language
914 mode; it's probably the right one. */
915 if (PLAIN_LANGUAGE_MODE == window->languageMode || window->filenameSet) {
916 DetermineLanguageMode(window, False);
918 window->filenameSet = True;
920 /* Update the stats line and window title with the new filename */
921 UpdateWindowTitle(window);
922 UpdateStatsLine(window);
924 SortTabBar(window);
925 return retVal;
928 static int doSave(WindowInfo *window)
930 char *fileString = NULL;
931 char fullname[MAXPATHLEN];
932 struct stat statbuf;
933 FILE *fp;
934 int fileLen, result;
936 /* Get the full name of the file */
937 strcpy(fullname, window->path);
938 strcat(fullname, window->filename);
940 /* Check for root and warn him if he wants to write to a file with
941 none of the write bits set. */
942 if ((0 == getuid())
943 && (0 == stat(fullname, &statbuf))
944 && !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH)))
946 result = DialogF(DF_WARN, window->shell, 2, "Writing Read-only File",
947 "File '%s' is marked as read-only.\n"
948 "Do you want to save anyway?",
949 "Save", "Cancel", window->filename);
950 if (1 != result)
952 return True;
956 #ifdef VMS
957 /* strip the version number from the file so VMS will begin a new one */
958 removeVersionNumber(fullname);
959 #endif
961 /* add a terminating newline if the file doesn't already have one for
962 Unix utilities which get confused otherwise
963 NOTE: this must be done _before_ we create/open the file, because the
964 (potential) buffer modification can trigger a check for file
965 changes. If the file is created for the first time, it has
966 zero size on disk, and the check would falsely conclude that the
967 file has changed on disk, and would pop up a warning dialog */
968 if (BufGetCharacter(window->buffer, window->buffer->length - 1) != '\n'
969 && window->buffer->length != 0
970 && GetPrefAppendLF())
972 BufInsert(window->buffer, window->buffer->length, "\n");
975 /* open the file */
976 #ifdef VMS
977 fp = fopen(fullname, "w", "rfm = stmlf");
978 #else
979 fp = fopen(fullname, "wb");
980 #endif /* VMS */
981 if (fp == NULL)
983 result = DialogF(DF_WARN, window->shell, 2, "Error saving File",
984 "Unable to save %s:\n%s\n\nSave as a new file?",
985 "Save As...", "Cancel",
986 window->filename, errorString());
988 if (result == 1)
990 return SaveWindowAs(window, NULL, 0);
992 return FALSE;
995 #ifdef VMS
996 /* get the complete name of the file including the new version number */
997 fgetname(fp, fullname);
998 #endif
1000 /* get the text buffer contents and its length */
1001 fileString = BufGetAll(window->buffer);
1002 fileLen = window->buffer->length;
1004 /* If null characters are substituted for, put them back */
1005 BufUnsubstituteNullChars(fileString, window->buffer);
1007 /* If the file is to be saved in DOS or Macintosh format, reconvert */
1008 if (window->fileFormat == DOS_FILE_FORMAT)
1010 if (!ConvertToDosFileString(&fileString, &fileLen))
1012 DialogF(DF_ERR, window->shell, 1, "Out of Memory",
1013 "Out of memory! Try\nsaving in Unix format", "OK");
1014 return FALSE;
1016 } else if (window->fileFormat == MAC_FILE_FORMAT)
1018 ConvertToMacFileString(fileString, fileLen);
1021 /* write to the file */
1022 #ifdef IBM_FWRITE_BUG
1023 write(fileno(fp), fileString, fileLen);
1024 #else
1025 fwrite(fileString, sizeof(char), fileLen, fp);
1026 #endif
1027 if (ferror(fp))
1029 DialogF(DF_ERR, window->shell, 1, "Error saving File",
1030 "%s not saved:\n%s", "OK", window->filename, errorString());
1031 fclose(fp);
1032 remove(fullname);
1033 XtFree(fileString);
1034 return FALSE;
1037 /* close the file */
1038 if (fclose(fp) != 0)
1040 DialogF(DF_ERR, window->shell, 1, "Error closing File",
1041 "Error closing file:\n%s", "OK", errorString());
1042 XtFree(fileString);
1043 return FALSE;
1046 /* free the text buffer copy returned from XmTextGetString */
1047 XtFree(fileString);
1049 #ifdef VMS
1050 /* reflect the fact that NEdit is now editing a new version of the file */
1051 ParseFilename(fullname, window->filename, window->path);
1052 #endif /*VMS*/
1054 /* success, file was written */
1055 SetWindowModified(window, FALSE);
1057 /* update the modification time */
1058 if (stat(fullname, &statbuf) == 0) {
1059 window->lastModTime = statbuf.st_mtime;
1060 window->fileMissing = FALSE;
1061 window->device = statbuf.st_dev;
1062 window->inode = statbuf.st_ino;
1063 } else {
1064 /* This needs to produce an error message -- the file can't be
1065 accessed! */
1066 window->lastModTime = 0;
1067 window->fileMissing = TRUE;
1068 window->device = 0;
1069 window->inode = 0;
1072 return TRUE;
1076 ** Create a backup file for the current window. The name for the backup file
1077 ** is generated using the name and path stored in the window and adding a
1078 ** tilde (~) on UNIX and underscore (_) on VMS to the beginning of the name.
1080 int WriteBackupFile(WindowInfo *window)
1082 char *fileString = NULL;
1083 char name[MAXPATHLEN];
1084 FILE *fp;
1085 int fd, fileLen;
1087 /* Generate a name for the autoSave file */
1088 backupFileName(window, name, sizeof(name));
1090 /* remove the old backup file.
1091 Well, this might fail - we'll notice later however. */
1092 remove(name);
1094 /* open the file, set more restrictive permissions (using default
1095 permissions was somewhat of a security hole, because permissions were
1096 independent of those of the original file being edited */
1097 #ifdef VMS
1098 if ((fp = fopen(name, "w", "rfm = stmlf")) == NULL)
1099 #else
1100 if ((fd = open(name, O_CREAT|O_EXCL|O_WRONLY, S_IRUSR | S_IWUSR)) < 0
1101 || (fp = fdopen(fd, "w")) == NULL)
1102 #endif /* VMS */
1104 DialogF(DF_WARN, window->shell, 1, "Error writing Backup",
1105 "Unable to save backup for %s:\n%s\n"
1106 "Automatic backup is now off", "OK", window->filename,
1107 errorString());
1108 window->autoSave = FALSE;
1109 SetToggleButtonState(window, window->autoSaveItem, FALSE, FALSE);
1110 return FALSE;
1113 /* Set VMS permissions */
1114 #ifdef VMS
1115 chmod(name, S_IRUSR | S_IWUSR);
1116 #endif
1118 /* get the text buffer contents and its length */
1119 fileString = BufGetAll(window->buffer);
1120 fileLen = window->buffer->length;
1122 /* If null characters are substituted for, put them back */
1123 BufUnsubstituteNullChars(fileString, window->buffer);
1125 /* add a terminating newline if the file doesn't already have one */
1126 if (fileLen != 0 && fileString[fileLen-1] != '\n')
1127 fileString[fileLen++] = '\n'; /* null terminator no longer needed */
1129 /* write out the file */
1130 #ifdef IBM_FWRITE_BUG
1131 write(fileno(fp), fileString, fileLen);
1132 #else
1133 fwrite(fileString, sizeof(char), fileLen, fp);
1134 #endif
1135 if (ferror(fp))
1137 DialogF(DF_ERR, window->shell, 1, "Error saving Backup",
1138 "Error while saving backup for %s:\n%s\n"
1139 "Automatic backup is now off", "OK", window->filename,
1140 errorString());
1141 fclose(fp);
1142 remove(name);
1143 XtFree(fileString);
1144 window->autoSave = FALSE;
1145 return FALSE;
1148 /* close the backup file */
1149 if (fclose(fp) != 0) {
1150 XtFree(fileString);
1151 return FALSE;
1154 /* Free the text buffer copy returned from XmTextGetString */
1155 XtFree(fileString);
1157 return TRUE;
1161 ** Remove the backup file associated with this window
1163 void RemoveBackupFile(WindowInfo *window)
1165 char name[MAXPATHLEN];
1167 /* Don't delete backup files when backups aren't activated. */
1168 if (window->autoSave == FALSE)
1169 return;
1171 backupFileName(window, name, sizeof(name));
1172 remove(name);
1176 ** Generate the name of the backup file for this window from the filename
1177 ** and path in the window data structure & write into name
1179 static void backupFileName(WindowInfo *window, char *name, size_t len)
1181 char bckname[MAXPATHLEN];
1182 #ifdef VMS
1183 if (window->filenameSet)
1184 sprintf(name, "%s_%s", window->path, window->filename);
1185 else
1186 sprintf(name, "%s_%s", "SYS$LOGIN:", window->filename);
1187 #else
1188 if (window->filenameSet)
1190 sprintf(name, "%s~%s", window->path, window->filename);
1191 } else
1193 strcpy(bckname, "~");
1194 strncat(bckname, window->filename, MAXPATHLEN - 1);
1195 PrependHome(bckname, name, len);
1197 #endif /*VMS*/
1201 ** If saveOldVersion is on, copies the existing version of the file to
1202 ** <filename>.bck in anticipation of a new version being saved. Returns
1203 ** True if backup fails and user requests that the new file not be written.
1205 static int writeBckVersion(WindowInfo *window)
1207 #ifndef VMS
1208 char fullname[MAXPATHLEN], bckname[MAXPATHLEN];
1209 struct stat statbuf;
1210 int in_fd, out_fd;
1211 char *io_buffer;
1212 #define IO_BUFFER_SIZE ((size_t)(1024*1024))
1214 /* Do only if version backups are turned on */
1215 if (!window->saveOldVersion) {
1216 return False;
1219 /* Get the full name of the file */
1220 strcpy(fullname, window->path);
1221 strcat(fullname, window->filename);
1223 /* Generate name for old version */
1224 if ((strlen(fullname) + 5) > (size_t) MAXPATHLEN) {
1225 return bckError(window, "file name too long", window->filename);
1227 sprintf(bckname, "%s.bck", fullname);
1229 /* Delete the old backup file */
1230 /* Errors are ignored; we'll notice them later. */
1231 remove(bckname);
1233 /* open the file being edited. If there are problems with the
1234 old file, don't bother the user, just skip the backup */
1235 in_fd = open(fullname, O_RDONLY);
1236 if (in_fd<0) {
1237 return FALSE;
1240 /* Get permissions of the file.
1241 We preserve the normal permissions but not ownership, extended
1242 attributes, et cetera. */
1243 if (fstat(in_fd, &statbuf) != 0) {
1244 return FALSE;
1247 /* open the destination file exclusive and with restrictive permissions. */
1248 out_fd = open(bckname, O_CREAT|O_EXCL|O_TRUNC|O_WRONLY, S_IRUSR | S_IWUSR);
1249 if (out_fd < 0) {
1250 return bckError(window, "Error open backup file", bckname);
1253 /* Set permissions on new file */
1254 if (fchmod(out_fd, statbuf.st_mode) != 0) {
1255 close(in_fd);
1256 close(out_fd);
1257 remove(bckname);
1258 return bckError(window, "fchmod() failed", bckname);
1261 /* Allocate I/O buffer */
1262 io_buffer = (char*) malloc(IO_BUFFER_SIZE);
1263 if (NULL == io_buffer) {
1264 close(in_fd);
1265 close(out_fd);
1266 remove(bckname);
1267 return bckError(window, "out of memory", bckname);
1270 /* copy loop */
1271 for(;;) {
1272 ssize_t bytes_read;
1273 ssize_t bytes_written;
1274 bytes_read = read(in_fd, io_buffer, IO_BUFFER_SIZE);
1276 if (bytes_read < 0) {
1277 close(in_fd);
1278 close(out_fd);
1279 remove(bckname);
1280 free(io_buffer);
1281 return bckError(window, "read() error", window->filename);
1284 if (0 == bytes_read) {
1285 break; /* EOF */
1288 /* write to the file */
1289 bytes_written = write(out_fd, io_buffer, (size_t) bytes_read);
1290 if (bytes_written != bytes_read) {
1291 close(in_fd);
1292 close(out_fd);
1293 remove(bckname);
1294 free(io_buffer);
1295 return bckError(window, errorString(), bckname);
1299 /* close the input and output files */
1300 close(in_fd);
1301 close(out_fd);
1303 free(io_buffer);
1305 #endif /* VMS */
1307 return FALSE;
1311 ** Error processing for writeBckVersion, gives the user option to cancel
1312 ** the subsequent save, or continue and optionally turn off versioning
1314 static int bckError(WindowInfo *window, const char *errString, const char *file)
1316 int resp;
1318 resp = DialogF(DF_ERR, window->shell, 3, "Error writing Backup",
1319 "Couldn't write .bck (last version) file.\n%s: %s", "Cancel Save",
1320 "Turn off Backups", "Continue", file, errString);
1321 if (resp == 1)
1322 return TRUE;
1323 if (resp == 2) {
1324 window->saveOldVersion = FALSE;
1325 #ifndef VMS
1326 SetToggleButtonState(window, window->saveLastItem, FALSE, FALSE);
1327 #endif
1329 return FALSE;
1332 void PrintWindow(WindowInfo *window, int selectedOnly)
1334 textBuffer *buf = window->buffer;
1335 selection *sel = &buf->primary;
1336 char *fileString = NULL;
1337 int fileLen;
1339 /* get the contents of the text buffer from the text area widget. Add
1340 wrapping newlines if necessary to make it match the displayed text */
1341 if (selectedOnly) {
1342 if (!sel->selected) {
1343 XBell(TheDisplay, 0);
1344 return;
1346 if (sel->rectangular) {
1347 fileString = BufGetSelectionText(buf);
1348 fileLen = strlen(fileString);
1349 } else
1350 fileString = TextGetWrapped(window->textArea, sel->start, sel->end,
1351 &fileLen);
1352 } else
1353 fileString = TextGetWrapped(window->textArea, 0, buf->length, &fileLen);
1355 /* If null characters are substituted for, put them back */
1356 BufUnsubstituteNullChars(fileString, buf);
1358 /* add a terminating newline if the file doesn't already have one */
1359 if (fileLen != 0 && fileString[fileLen-1] != '\n')
1360 fileString[fileLen++] = '\n'; /* null terminator no longer needed */
1362 /* Print the string */
1363 PrintString(fileString, fileLen, window->shell, window->filename);
1365 /* Free the text buffer copy returned from XmTextGetString */
1366 XtFree(fileString);
1370 ** Print a string (length is required). parent is the dialog parent, for
1371 ** error dialogs, and jobName is the print title.
1373 void PrintString(const char *string, int length, Widget parent, const char *jobName)
1375 char tmpFileName[L_tmpnam]; /* L_tmpnam defined in stdio.h */
1376 FILE *fp;
1377 int fd;
1379 /* Generate a temporary file name */
1380 /* If the glibc is used, the linker issues a warning at this point. This is
1381 very thoughtful of him, but does not apply to NEdit. The recommended
1382 replacement mkstemp(3) uses the same algorithm as NEdit, namely
1383 1. Create a filename
1384 2. Open the file with the O_CREAT|O_EXCL flags
1385 So all an attacker can do is a DoS on the print function. */
1386 tmpnam(tmpFileName);
1388 /* open the temporary file */
1389 #ifdef VMS
1390 if ((fp = fopen(tmpFileName, "w", "rfm = stmlf")) == NULL)
1391 #else
1392 if ((fd = open(tmpFileName, O_CREAT|O_EXCL|O_WRONLY, S_IRUSR | S_IWUSR)) < 0 || (fp = fdopen(fd, "w")) == NULL)
1393 #endif /* VMS */
1395 DialogF(DF_WARN, parent, 1, "Error while Printing",
1396 "Unable to write file for printing:\n%s", "OK",
1397 errorString());
1398 return;
1401 #ifdef VMS
1402 chmod(tmpFileName, S_IRUSR | S_IWUSR);
1403 #endif
1405 /* write to the file */
1406 #ifdef IBM_FWRITE_BUG
1407 write(fileno(fp), string, length);
1408 #else
1409 fwrite(string, sizeof(char), length, fp);
1410 #endif
1411 if (ferror(fp))
1413 DialogF(DF_ERR, parent, 1, "Error while Printing",
1414 "%s not printed:\n%s", "OK", jobName, errorString());
1415 fclose(fp); /* should call close(fd) in turn! */
1416 remove(tmpFileName);
1417 return;
1420 /* close the temporary file */
1421 if (fclose(fp) != 0)
1423 DialogF(DF_ERR, parent, 1, "Error while Printing",
1424 "Error closing temp. print file:\n%s", "OK",
1425 errorString());
1426 remove(tmpFileName);
1427 return;
1430 /* Print the temporary file, then delete it and return success */
1431 #ifdef VMS
1432 strcat(tmpFileName, ".");
1433 PrintFile(parent, tmpFileName, jobName, True);
1434 #else
1435 PrintFile(parent, tmpFileName, jobName);
1436 remove(tmpFileName);
1437 #endif /*VMS*/
1438 return;
1442 ** Wrapper for GetExistingFilename which uses the current window's path
1443 ** (if set) as the default directory.
1445 int PromptForExistingFile(WindowInfo *window, char *prompt, char *fullname)
1447 char *savedDefaultDir;
1448 int retVal;
1450 /* Temporarily set default directory to window->path, prompt for file,
1451 then, if the call was unsuccessful, restore the original default
1452 directory */
1453 savedDefaultDir = GetFileDialogDefaultDirectory();
1454 if (*window->path != '\0')
1455 SetFileDialogDefaultDirectory(window->path);
1456 retVal = GetExistingFilename(window->shell, prompt, fullname);
1457 if (retVal != GFN_OK)
1458 SetFileDialogDefaultDirectory(savedDefaultDir);
1460 XtFree(savedDefaultDir);
1462 return retVal;
1466 ** Wrapper for HandleCustomNewFileSB which uses the current window's path
1467 ** (if set) as the default directory, and asks about embedding newlines
1468 ** to make wrapping permanent.
1470 int PromptForNewFile(WindowInfo *window, char *prompt, char *fullname,
1471 int *fileFormat, int *addWrap)
1473 int n, retVal;
1474 Arg args[20];
1475 XmString s1, s2;
1476 Widget fileSB, wrapToggle;
1477 Widget formatForm, formatBtns, unixFormat, dosFormat, macFormat;
1478 char *savedDefaultDir;
1480 *fileFormat = window->fileFormat;
1482 /* Temporarily set default directory to window->path, prompt for file,
1483 then, if the call was unsuccessful, restore the original default
1484 directory */
1485 savedDefaultDir = GetFileDialogDefaultDirectory();
1486 if (*window->path != '\0')
1487 SetFileDialogDefaultDirectory(window->path);
1489 /* Present a file selection dialog with an added field for requesting
1490 long line wrapping to become permanent via inserted newlines */
1491 n = 0;
1492 XtSetArg(args[n],
1493 XmNselectionLabelString,
1494 s1 = XmStringCreateLocalized("New File Name:")); n++;
1495 XtSetArg(args[n], XmNdialogStyle, XmDIALOG_FULL_APPLICATION_MODAL); n++;
1496 XtSetArg(args[n],
1497 XmNdialogTitle,
1498 s2 = XmStringCreateSimple(prompt)); n++;
1499 fileSB = CreateFileSelectionDialog(window->shell,"FileSelect",args,n);
1500 XmStringFree(s1);
1501 XmStringFree(s2);
1502 formatForm = XtVaCreateManagedWidget("formatForm", xmFormWidgetClass,
1503 fileSB, NULL);
1504 formatBtns = XtVaCreateManagedWidget("formatBtns",
1505 xmRowColumnWidgetClass, formatForm,
1506 XmNradioBehavior, XmONE_OF_MANY,
1507 XmNorientation, XmHORIZONTAL,
1508 XmNpacking, XmPACK_TIGHT,
1509 XmNtopAttachment, XmATTACH_FORM,
1510 XmNleftAttachment, XmATTACH_FORM,
1511 NULL);
1512 XtVaCreateManagedWidget("formatBtns", xmLabelWidgetClass, formatBtns,
1513 XmNlabelString, s1=XmStringCreateSimple("Format:"), NULL);
1514 XmStringFree(s1);
1515 unixFormat = XtVaCreateManagedWidget("unixFormat",
1516 xmToggleButtonWidgetClass, formatBtns,
1517 XmNlabelString, s1 = XmStringCreateSimple("Unix"),
1518 XmNset, *fileFormat == UNIX_FILE_FORMAT,
1519 XmNuserData, (XtPointer)UNIX_FILE_FORMAT,
1520 XmNmarginHeight, 0,
1521 XmNalignment, XmALIGNMENT_BEGINNING,
1522 XmNmnemonic, 'U',
1523 NULL);
1524 XmStringFree(s1);
1525 XtAddCallback(unixFormat, XmNvalueChangedCallback, setFormatCB,
1526 fileFormat);
1527 dosFormat = XtVaCreateManagedWidget("dosFormat",
1528 xmToggleButtonWidgetClass, formatBtns,
1529 XmNlabelString, s1 = XmStringCreateSimple("DOS"),
1530 XmNset, *fileFormat == DOS_FILE_FORMAT,
1531 XmNuserData, (XtPointer)DOS_FILE_FORMAT,
1532 XmNmarginHeight, 0,
1533 XmNalignment, XmALIGNMENT_BEGINNING,
1534 XmNmnemonic, 'O',
1535 NULL);
1536 XmStringFree(s1);
1537 XtAddCallback(dosFormat, XmNvalueChangedCallback, setFormatCB,
1538 fileFormat);
1539 macFormat = XtVaCreateManagedWidget("macFormat",
1540 xmToggleButtonWidgetClass, formatBtns,
1541 XmNlabelString, s1 = XmStringCreateSimple("Macintosh"),
1542 XmNset, *fileFormat == MAC_FILE_FORMAT,
1543 XmNuserData, (XtPointer)MAC_FILE_FORMAT,
1544 XmNmarginHeight, 0,
1545 XmNalignment, XmALIGNMENT_BEGINNING,
1546 XmNmnemonic, 'M',
1547 NULL);
1548 XmStringFree(s1);
1549 XtAddCallback(macFormat, XmNvalueChangedCallback, setFormatCB,
1550 fileFormat);
1551 if (window->wrapMode == CONTINUOUS_WRAP) {
1552 wrapToggle = XtVaCreateManagedWidget("addWrap",
1553 xmToggleButtonWidgetClass, formatForm,
1554 XmNlabelString, s1 = XmStringCreateSimple("Add line breaks where wrapped"),
1555 XmNalignment, XmALIGNMENT_BEGINNING,
1556 XmNmnemonic, 'A',
1557 XmNtopAttachment, XmATTACH_WIDGET,
1558 XmNtopWidget, formatBtns,
1559 XmNleftAttachment, XmATTACH_FORM,
1560 NULL);
1561 XtAddCallback(wrapToggle, XmNvalueChangedCallback, addWrapCB,
1562 addWrap);
1563 XmStringFree(s1);
1565 *addWrap = False;
1566 XtVaSetValues(XmFileSelectionBoxGetChild(fileSB, XmDIALOG_FILTER_LABEL),
1567 XmNmnemonic, 'l',
1568 XmNuserData, XmFileSelectionBoxGetChild(fileSB, XmDIALOG_FILTER_TEXT),
1569 NULL);
1570 XtVaSetValues(XmFileSelectionBoxGetChild(fileSB, XmDIALOG_DIR_LIST_LABEL),
1571 XmNmnemonic, 'D',
1572 XmNuserData, XmFileSelectionBoxGetChild(fileSB, XmDIALOG_DIR_LIST),
1573 NULL);
1574 XtVaSetValues(XmFileSelectionBoxGetChild(fileSB, XmDIALOG_LIST_LABEL),
1575 XmNmnemonic, 'F',
1576 XmNuserData, XmFileSelectionBoxGetChild(fileSB, XmDIALOG_LIST),
1577 NULL);
1578 XtVaSetValues(XmFileSelectionBoxGetChild(fileSB, XmDIALOG_SELECTION_LABEL),
1579 XmNmnemonic, prompt[strspn(prompt, "lFD")],
1580 XmNuserData, XmFileSelectionBoxGetChild(fileSB, XmDIALOG_TEXT),
1581 NULL);
1582 AddDialogMnemonicHandler(fileSB, FALSE);
1583 RemapDeleteKey(XmFileSelectionBoxGetChild(fileSB, XmDIALOG_FILTER_TEXT));
1584 RemapDeleteKey(XmFileSelectionBoxGetChild(fileSB, XmDIALOG_TEXT));
1585 retVal = HandleCustomNewFileSB(fileSB, fullname,
1586 window->filenameSet ? window->filename : NULL);
1588 if (retVal != GFN_OK)
1589 SetFileDialogDefaultDirectory(savedDefaultDir);
1591 XtFree(savedDefaultDir);
1593 return retVal;
1597 ** Find a name for an untitled file, unique in the name space of in the opened
1598 ** files in this session, i.e. Untitled or Untitled_nn, and write it into
1599 ** the string "name".
1601 void UniqueUntitledName(char *name)
1603 WindowInfo *w;
1604 int i;
1606 for (i=0; i<INT_MAX; i++) {
1607 if (i == 0)
1608 sprintf(name, "Untitled");
1609 else
1610 sprintf(name, "Untitled_%d", i);
1611 for (w=WindowList; w!=NULL; w=w->next)
1612 if (!strcmp(w->filename, name))
1613 break;
1614 if (w == NULL)
1615 break;
1620 ** Callback that guards us from trying to access a window after it has
1621 ** been destroyed while a modal dialog is up.
1623 static void modifiedWindowDestroyedCB(Widget w, XtPointer clientData,
1624 XtPointer callData)
1626 *(Bool*)clientData = TRUE;
1630 ** Check if the file in the window was changed by an external source.
1631 ** and put up a warning dialog if it has.
1633 void CheckForChangesToFile(WindowInfo *window)
1635 static WindowInfo* lastCheckWindow = NULL;
1636 static Time lastCheckTime = 0;
1637 char fullname[MAXPATHLEN];
1638 struct stat statbuf;
1639 Time timestamp;
1640 FILE *fp;
1641 int resp, silent = 0;
1642 XWindowAttributes winAttr;
1643 Boolean windowIsDestroyed = False;
1645 if(!window->filenameSet)
1646 return;
1648 /* If last check was very recent, don't impact performance */
1649 timestamp = XtLastTimestampProcessed(XtDisplay(window->shell));
1650 if (window == lastCheckWindow &&
1651 timestamp - lastCheckTime < MOD_CHECK_INTERVAL)
1652 return;
1653 lastCheckWindow = window;
1654 lastCheckTime = timestamp;
1656 /* Update the status, but don't pop up a dialog if we're called
1657 from a place where the window might be iconic (e.g., from the
1658 replace dialog) or on another desktop.
1660 This works, but I bet it costs a round-trip to the server.
1661 Might be better to capture MapNotify/Unmap events instead.
1663 For tabs that are not on top, we don't want the dialog either,
1664 and we don't even need to contact the server to find out. By
1665 performing this check first, we avoid a server round-trip for
1666 most files in practice. */
1667 if (!IsTopDocument(window))
1668 silent = 1;
1669 else {
1670 XGetWindowAttributes(XtDisplay(window->shell),
1671 XtWindow(window->shell),
1672 &winAttr);
1674 if (winAttr.map_state != IsViewable)
1675 silent = 1;
1678 /* Get the file mode and modification time */
1679 strcpy(fullname, window->path);
1680 strcat(fullname, window->filename);
1681 if (stat(fullname, &statbuf) != 0) {
1682 /* Return if we've already warned the user or we can't warn him now */
1683 if (window->fileMissing || silent) {
1684 return;
1687 /* Can't stat the file -- maybe it's been deleted.
1688 The filename is now invalid */
1689 window->fileMissing = TRUE;
1690 window->lastModTime = 1;
1691 window->device = 0;
1692 window->inode = 0;
1694 /* Warn the user, if they like to be warned (Maybe this should be its
1695 own preference setting: GetPrefWarnFileDeleted()) */
1696 if (GetPrefWarnFileMods()) {
1697 char* title;
1698 char* body;
1700 /* See note below about pop-up timing and XUngrabPointer */
1701 XUngrabPointer(XtDisplay(window->shell), timestamp);
1703 /* If the window (and the dialog) are destroyed while the dialog
1704 is up (typically closed via the window manager), we should
1705 avoid accessing the window afterwards. */
1706 XtAddCallback(window->shell, XmNdestroyCallback,
1707 modifiedWindowDestroyedCB, &windowIsDestroyed);
1709 /* Set title, message body and button to match stat()'s error. */
1710 switch (errno) {
1711 case ENOENT:
1712 /* A component of the path file_name does not exist. */
1713 title = "File not Found";
1714 body = "File '%s' (or directory in its path)\n"
1715 "no longer exists.\n"
1716 "Another program may have deleted or moved it.";
1717 resp = DialogF(DF_ERR, window->shell, 2, title, body,
1718 "Save", "Cancel", window->filename);
1719 break;
1720 case EACCES:
1721 /* Search permission denied for a path component. We add
1722 one to the response because Re-Save wouldn't really
1723 make sense here. */
1724 title = "Permission Denied";
1725 body = "You no longer have access to file '%s'.\n"
1726 "Another program may have changed the permissions of\n"
1727 "one of its parent directories.";
1728 resp = 1 + DialogF(DF_ERR, window->shell, 1, title, body,
1729 "Cancel", window->filename);
1730 break;
1731 default:
1732 /* Everything else. This hints at an internal error (eg.
1733 ENOTDIR) or at some bad state at the host. */
1734 title = "File not Accessible";
1735 body = "Error while checking the status of file '%s':\n"
1736 " '%s'\n"
1737 "Please make sure that no data is lost before closing\n"
1738 "this window.";
1739 resp = DialogF(DF_ERR, window->shell, 2, title, body,
1740 "Save", "Cancel", window->filename,
1741 errorString());
1742 break;
1745 if (!windowIsDestroyed) {
1746 XtRemoveCallback(window->shell, XmNdestroyCallback,
1747 modifiedWindowDestroyedCB, &windowIsDestroyed);
1750 switch (resp) {
1751 case 1:
1752 SaveWindow(window);
1753 break;
1754 /* Good idea, but this leads to frequent crashes, see
1755 SF#1578869. Reinsert this if circumstances change by
1756 uncommenting this part and inserting a "Close" button
1757 before each Cancel button above.
1758 case 2:
1759 CloseWindow(window);
1760 return;
1765 /* A missing or (re-)saved file can't be read-only. */
1766 /* TODO: A document without a file can be locked though. */
1767 /* Make sure that the window was not destroyed behind our back! */
1768 if (!windowIsDestroyed) {
1769 SET_PERM_LOCKED(window->lockReasons, False);
1770 UpdateWindowTitle(window);
1771 UpdateWindowReadOnly(window);
1773 return;
1776 /* Check that the file's read-only status is still correct (but
1777 only if the file can still be opened successfully in read mode) */
1778 if (window->fileMode != statbuf.st_mode ||
1779 window->fileUid != statbuf.st_uid ||
1780 window->fileGid != statbuf.st_gid) {
1781 window->fileMode = statbuf.st_mode;
1782 window->fileUid = statbuf.st_uid;
1783 window->fileGid = statbuf.st_gid;
1784 if ((fp = fopen(fullname, "r")) != NULL) {
1785 int readOnly;
1786 fclose(fp);
1787 #ifndef DONT_USE_ACCESS
1788 readOnly = access(fullname, W_OK) != 0;
1789 #else
1790 if (((fp = fopen(fullname, "r+")) != NULL)) {
1791 readOnly = FALSE;
1792 fclose(fp);
1793 } else
1794 readOnly = TRUE;
1795 #endif
1796 if (IS_PERM_LOCKED(window->lockReasons) != readOnly) {
1797 SET_PERM_LOCKED(window->lockReasons, readOnly);
1798 UpdateWindowTitle(window);
1799 UpdateWindowReadOnly(window);
1804 /* Warn the user if the file has been modified, unless checking is
1805 turned off or the user has already been warned. Popping up a dialog
1806 from a focus callback (which is how this routine is usually called)
1807 seems to catch Motif off guard, and if the timing is just right, the
1808 dialog can be left with a still active pointer grab from a Motif menu
1809 which is still in the process of popping down. The workaround, below,
1810 of calling XUngrabPointer is inelegant but seems to fix the problem. */
1811 if (!silent &&
1812 ((window->lastModTime != 0 &&
1813 window->lastModTime != statbuf.st_mtime) ||
1814 window->fileMissing) ){
1815 window->lastModTime = 0; /* Inhibit further warnings */
1816 window->fileMissing = FALSE;
1817 if (!GetPrefWarnFileMods())
1818 return;
1819 if (GetPrefWarnRealFileMods() &&
1820 !cmpWinAgainstFile(window, fullname)) {
1821 /* Contents hasn't changed. Update the modification time. */
1822 window->lastModTime = statbuf.st_mtime;
1823 return;
1825 XUngrabPointer(XtDisplay(window->shell), timestamp);
1826 if (window->fileChanged)
1827 resp = DialogF(DF_WARN, window->shell, 2,
1828 "File modified externally",
1829 "%s has been modified by another program. Reload?\n\n"
1830 "WARNING: Reloading will discard changes made in this\n"
1831 "editing session!", "Reload", "Cancel", window->filename);
1832 else
1833 resp = DialogF(DF_WARN, window->shell, 2,
1834 "File modified externally",
1835 "%s has been modified by another\nprogram. Reload?",
1836 "Reload", "Cancel", window->filename);
1837 if (resp == 1)
1838 RevertToSaved(window);
1843 ** Return true if the file displayed in window has been modified externally
1844 ** to nedit. This should return FALSE if the file has been deleted or is
1845 ** unavailable.
1847 static int fileWasModifiedExternally(WindowInfo *window)
1849 char fullname[MAXPATHLEN];
1850 struct stat statbuf;
1852 if(!window->filenameSet)
1853 return FALSE;
1854 /* if (window->lastModTime == 0)
1855 return FALSE; */
1856 strcpy(fullname, window->path);
1857 strcat(fullname, window->filename);
1858 if (stat(fullname, &statbuf) != 0)
1859 return FALSE;
1860 if (window->lastModTime == statbuf.st_mtime)
1861 return FALSE;
1862 if (GetPrefWarnRealFileMods() &&
1863 !cmpWinAgainstFile(window, fullname)) {
1864 return FALSE;
1866 return TRUE;
1870 ** Check the read-only or locked status of the window and beep and return
1871 ** false if the window should not be written in.
1873 int CheckReadOnly(WindowInfo *window)
1875 if (IS_ANY_LOCKED(window->lockReasons)) {
1876 XBell(TheDisplay, 0);
1877 return True;
1879 return False;
1883 ** Wrapper for strerror so all the calls don't have to be ifdef'd for VMS.
1885 static const char *errorString(void)
1887 #ifdef VMS
1888 return strerror(errno, vaxc$errno);
1889 #else
1890 return strerror(errno);
1891 #endif
1894 #ifdef VMS
1896 ** Removing the VMS version number from a file name (if has one).
1898 void removeVersionNumber(char *fileName)
1900 char *versionStart;
1902 versionStart = strrchr(fileName, ';');
1903 if (versionStart != NULL)
1904 *versionStart = '\0';
1906 #endif /*VMS*/
1909 ** Callback procedure for File Format toggle buttons. Format is stored
1910 ** in userData field of widget button
1912 static void setFormatCB(Widget w, XtPointer clientData, XtPointer callData)
1914 if (XmToggleButtonGetState(w)) {
1915 XtPointer userData;
1916 XtVaGetValues(w, XmNuserData, &userData, NULL);
1917 *(int*) clientData = (int) userData;
1922 ** Callback procedure for toggle button requesting newlines to be inserted
1923 ** to emulate continuous wrapping.
1925 static void addWrapCB(Widget w, XtPointer clientData, XtPointer callData)
1927 int resp;
1928 int *addWrap = (int *)clientData;
1930 if (XmToggleButtonGetState(w))
1932 resp = DialogF(DF_WARN, w, 2, "Add Wrap",
1933 "This operation adds permanent line breaks to\n"
1934 "match the automatic wrapping done by the\n"
1935 "Continuous Wrap mode Preferences Option.\n\n"
1936 "*** This Option is Irreversable ***\n\n"
1937 "Once newlines are inserted, continuous wrapping\n"
1938 "will no longer work automatically on these lines", "OK",
1939 "Cancel");
1940 if (resp == 2)
1942 XmToggleButtonSetState(w, False, False);
1943 *addWrap = False;
1944 } else
1946 *addWrap = True;
1948 } else
1950 *addWrap = False;
1955 ** Change a window created in NEdit's continuous wrap mode to the more
1956 ** conventional Unix format of embedded newlines. Indicate to the user
1957 ** by turning off Continuous Wrap mode.
1959 static void addWrapNewlines(WindowInfo *window)
1961 int fileLen, i, insertPositions[MAX_PANES], topLines[MAX_PANES];
1962 int horizOffset;
1963 Widget text;
1964 char *fileString;
1966 /* save the insert and scroll positions of each pane */
1967 for (i=0; i<=window->nPanes; i++) {
1968 text = i==0 ? window->textArea : window->textPanes[i-1];
1969 insertPositions[i] = TextGetCursorPos(text);
1970 TextGetScroll(text, &topLines[i], &horizOffset);
1973 /* Modify the buffer to add wrapping */
1974 fileString = TextGetWrapped(window->textArea, 0,
1975 window->buffer->length, &fileLen);
1976 BufSetAll(window->buffer, fileString);
1977 XtFree(fileString);
1979 /* restore the insert and scroll positions of each pane */
1980 for (i=0; i<=window->nPanes; i++) {
1981 text = i==0 ? window->textArea : window->textPanes[i-1];
1982 TextSetCursorPos(text, insertPositions[i]);
1983 TextSetScroll(text, topLines[i], 0);
1986 /* Show the user that something has happened by turning off
1987 Continuous Wrap mode */
1988 SetToggleButtonState(window, window->continuousWrapItem, False, True);
1992 * Number of bytes read at once by cmpWinAgainstFile
1994 #define PREFERRED_CMPBUF_LEN 32768
1997 * Check if the contens of the textBuffer *buf is equal
1998 * the contens of the file named fileName. The format of
1999 * the file (UNIX/DOS/MAC) is handled properly.
2001 * Return values
2002 * 0: no difference found
2003 * !0: difference found or could not compare contents.
2005 static int cmpWinAgainstFile(WindowInfo *window, const char *fileName)
2007 char fileString[PREFERRED_CMPBUF_LEN + 2];
2008 struct stat statbuf;
2009 int fileLen, restLen, nRead, bufPos, rv, offset, filePos;
2010 char pendingCR = 0;
2011 int fileFormat = window->fileFormat;
2012 char message[MAXPATHLEN+50];
2013 textBuffer *buf = window->buffer;
2014 FILE *fp;
2016 fp = fopen(fileName, "r");
2017 if (!fp)
2018 return (1);
2019 if (fstat(fileno(fp), &statbuf) != 0) {
2020 fclose(fp);
2021 return (1);
2024 fileLen = statbuf.st_size;
2025 /* For DOS files, we can't simply check the length */
2026 if (fileFormat != DOS_FILE_FORMAT) {
2027 if (fileLen != buf->length) {
2028 fclose(fp);
2029 return (1);
2031 } else {
2032 /* If a DOS file is smaller on disk, it's certainly different */
2033 if (fileLen < buf->length) {
2034 fclose(fp);
2035 return (1);
2039 /* For large files, the comparison can take a while. If it takes too long,
2040 the user should be given a clue about what is happening. */
2041 sprintf(message, "Comparing externally modified %s ...", window->filename);
2042 restLen = min(PREFERRED_CMPBUF_LEN, fileLen);
2043 bufPos = 0;
2044 filePos = 0;
2045 while (restLen > 0) {
2046 AllWindowsBusy(message);
2047 if (pendingCR) {
2048 fileString[0] = pendingCR;
2049 offset = 1;
2050 } else {
2051 offset = 0;
2054 nRead = fread(fileString+offset, sizeof(char), restLen, fp);
2055 if (nRead != restLen) {
2056 fclose(fp);
2057 AllWindowsUnbusy();
2058 return (1);
2060 filePos += nRead;
2062 nRead += offset;
2064 /* check for on-disk file format changes, but only for the first hunk */
2065 if (bufPos == 0 && fileFormat != FormatOfFile(fileString)) {
2066 fclose(fp);
2067 AllWindowsUnbusy();
2068 return (1);
2071 if (fileFormat == MAC_FILE_FORMAT)
2072 ConvertFromMacFileString(fileString, nRead);
2073 else if (fileFormat == DOS_FILE_FORMAT)
2074 ConvertFromDosFileString(fileString, &nRead, &pendingCR);
2076 /* Beware of 0 chars ! */
2077 BufSubstituteNullChars(fileString, nRead, buf);
2078 rv = BufCmp(buf, bufPos, nRead, fileString);
2079 if (rv) {
2080 fclose(fp);
2081 AllWindowsUnbusy();
2082 return (rv);
2084 bufPos += nRead;
2085 restLen = min(fileLen - filePos, PREFERRED_CMPBUF_LEN);
2087 AllWindowsUnbusy();
2088 fclose(fp);
2089 if (pendingCR) {
2090 rv = BufCmp(buf, bufPos, 1, &pendingCR);
2091 if (rv) {
2092 return (rv);
2094 bufPos += 1;
2096 if (bufPos != buf->length) {
2097 return (1);
2099 return (0);
2103 ** Force ShowLineNumbers() to re-evaluate line counts for the window if line
2104 ** counts are required.
2106 static void forceShowLineNumbers(WindowInfo *window)
2108 Boolean showLineNum = window->showLineNumbers;
2109 if (showLineNum) {
2110 window->showLineNumbers = False;
2111 ShowLineNumbers(window, showLineNum);
2115 static int min(int i1, int i2)
2117 return i1 <= i2 ? i1 : i2;