1 static const char CVSID
[] = "$Id: file.c,v 1.94 2005/05/27 16:49:04 edg Exp $";
2 /*******************************************************************************
4 * file.c -- Nirvana Editor file i/o *
6 * Copyright (C) 1999 Mark Edel *
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. *
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 *
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 *
23 * Nirvana Text Editor *
26 * Written by Mark Edel *
28 *******************************************************************************/
31 #include "../config.h"
38 #include "preferences.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"
57 #include "../util/VMSparam.h"
63 #include <sys/types.h>
66 #include <sys/param.h>
73 #include <Xm/ToggleB.h>
74 #include <Xm/FileSB.h>
75 #include <Xm/RowColumn.h>
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
,
93 static void backupFileName(WindowInfo
*window
, char *name
, int 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
);
105 void removeVersionNumber(char *fileName
);
108 WindowInfo
*EditNewFile(WindowInfo
*inWindow
, char *geometry
, int iconic
,
109 const char *languageMode
, const char *defaultPath
)
111 char name
[MAXPATHLEN
];
114 /*... test for creatability? */
116 /* Find a (relatively) unique name for the new file */
117 UniqueUntitledName(name
);
119 /* create new window/document */
121 window
= CreateDocument(inWindow
, name
, geometry
, iconic
);
123 window
= CreateWindow(name
, geometry
, iconic
);
125 strcpy(window
->filename
, name
);
126 strcpy(window
->path
, defaultPath
? defaultPath
: "");
127 SetWindowModified(window
, FALSE
);
128 CLEAR_ALL_LOCKS(window
->lockReasons
);
129 UpdateWindowReadOnly(window
);
130 UpdateStatsLine(window
);
131 UpdateWindowTitle(window
);
132 RefreshTabState(window
);
134 if (languageMode
== NULL
)
135 DetermineLanguageMode(window
, True
);
137 SetLanguageMode(window
, FindLanguageMode(languageMode
), True
);
139 ShowTabBar(window
, GetShowTabBar(window
));
141 if (iconic
&& IsIconic(window
))
142 RaiseDocument(window
);
144 RaiseDocumentWindow(window
);
151 ** Open an existing file specified by name and path. Use the window inWindow
152 ** unless inWindow is NULL or points to a window which is already in use
153 ** (displays a file other than Untitled, or is Untitled but modified). Flags
156 ** CREATE: If file is not found, (optionally) prompt the
157 ** user whether to create
158 ** SUPPRESS_CREATE_WARN When creating a file, don't ask the user
159 ** PREF_READ_ONLY Make the file read-only regardless
161 ** If languageMode is passed as NULL, it will be determined automatically
162 ** from the file extension or file contents.
164 ** If bgOpen is True, then the file will be open in background. This
165 ** works in association with the SetLanguageMode() function that has
166 ** the syntax highlighting deferred, in order to speed up the file-
167 ** opening operation when multiple files are being opened in succession.
169 WindowInfo
*EditExistingFile(WindowInfo
*inWindow
, const char *name
,
170 const char *path
, int flags
, char *geometry
, int iconic
,
171 const char *languageMode
, int tabbed
, int bgOpen
)
174 char fullname
[MAXPATHLEN
];
176 /* first look to see if file is already displayed in a window */
177 window
= FindWindowWithFile(name
, path
);
178 if (window
!= NULL
) {
181 RaiseDocument(window
);
183 RaiseDocumentWindow(window
);
188 /* If an existing window isn't specified; or the window is already
189 in use (not Untitled or Untitled and modified), or is currently
190 busy running a macro; create the window */
191 if (inWindow
== NULL
) {
192 window
= CreateWindow(name
, geometry
, iconic
);
194 else if (inWindow
->filenameSet
|| inWindow
->fileChanged
||
195 inWindow
->macroCmdData
!= NULL
) {
197 window
= CreateDocument(inWindow
, name
, geometry
, iconic
);
200 window
= CreateWindow(name
, geometry
, iconic
);
204 /* open file in untitled document */
206 strcpy(window
->path
, path
);
207 strcpy(window
->filename
, name
);
208 if (!iconic
&& !bgOpen
) {
209 RaiseDocumentWindow(window
);
214 if (!doOpen(window
, name
, path
, flags
)) {
215 /* The user may have destroyed the window instead of closing the
216 warning dialog; don't close it twice */
222 /* Decide what language mode to use, trigger language specific actions */
223 if (languageMode
== NULL
)
224 DetermineLanguageMode(window
, True
);
226 SetLanguageMode(window
, FindLanguageMode(languageMode
), True
);
228 /* update tab label and tooltip */
229 RefreshTabState(window
);
231 ShowTabBar(window
, GetShowTabBar(window
));
234 RaiseDocument(window
);
236 /* Bring the title bar and statistics line up to date, doOpen does
237 not necessarily set the window title or read-only status */
238 UpdateWindowTitle(window
);
239 UpdateWindowReadOnly(window
);
240 UpdateStatsLine(window
);
242 /* Add the name to the convenience menu of previously opened files */
243 strcpy(fullname
, path
);
244 strcat(fullname
, name
);
245 if(GetPrefAlwaysCheckRelTagsSpecs())
246 AddRelTagsFile(GetPrefTagFile(), path
, TAG
);
247 AddToPrevOpenMenu(fullname
);
252 void RevertToSaved(WindowInfo
*window
)
254 char name
[MAXPATHLEN
], path
[MAXPATHLEN
];
256 int insertPositions
[MAX_PANES
], topLines
[MAX_PANES
];
257 int horizOffsets
[MAX_PANES
];
261 /* Can't revert untitled windows */
262 if (!window
->filenameSet
)
264 DialogF(DF_WARN
, window
->shell
, 1, "Error",
265 "Window '%s' was never saved, can't re-read", "OK",
270 /* save insert & scroll positions of all of the panes to restore later */
271 for (i
=0; i
<=window
->nPanes
; i
++) {
272 text
= i
==0 ? window
->textArea
: window
->textPanes
[i
-1];
273 insertPositions
[i
] = TextGetCursorPos(text
);
274 TextGetScroll(text
, &topLines
[i
], &horizOffsets
[i
]);
277 /* re-read the file, update the window title if new file is different */
278 strcpy(name
, window
->filename
);
279 strcpy(path
, window
->path
);
280 RemoveBackupFile(window
);
281 ClearUndoList(window
);
282 openFlags
|= IS_USER_LOCKED(window
->lockReasons
) ? PREF_READ_ONLY
: 0;
283 if (!doOpen(window
, name
, path
, openFlags
)) {
284 /* This is a bit sketchy. The only error in doOpen that irreperably
285 damages the window is "too much binary data". It should be
286 pretty rare to be reverting something that was fine only to find
287 that now it has too much binary data. */
288 if (!window
->fileMissing
)
291 /* Treat it like an externally modified file */
292 window
->lastModTime
=0;
293 window
->fileMissing
=FALSE
;
297 UpdateWindowTitle(window
);
298 UpdateWindowReadOnly(window
);
300 /* restore the insert and scroll positions of each pane */
301 for (i
=0; i
<=window
->nPanes
; i
++) {
302 text
= i
==0 ? window
->textArea
: window
->textPanes
[i
-1];
303 TextSetCursorPos(text
, insertPositions
[i
]);
304 TextSetScroll(text
, topLines
[i
], horizOffsets
[i
]);
309 ** Checks whether a window is still alive, and closes it only if so.
310 ** Intended to be used when the file could not be opened for some reason.
311 ** Normally the window is still alive, but the user may have closed the
312 ** window instead of the error dialog. In that case, we shouldn't close the
313 ** window a second time.
315 static void safeClose(WindowInfo
*window
)
317 WindowInfo
* p
= WindowList
;
327 static int doOpen(WindowInfo
*window
, const char *name
, const char *path
,
330 char fullname
[MAXPATHLEN
];
332 int fileLen
, readLen
;
333 char *fileString
, *c
;
338 /* initialize lock reasons */
339 CLEAR_ALL_LOCKS(window
->lockReasons
);
341 /* Update the window data structure */
342 strcpy(window
->filename
, name
);
343 strcpy(window
->path
, path
);
344 window
->filenameSet
= TRUE
;
345 window
->fileMissing
= TRUE
;
347 /* Get the full name of the file */
348 strcpy(fullname
, path
);
349 strcat(fullname
, name
);
352 #ifndef DONT_USE_ACCESS
353 /* The only advantage of this is if you use clearcase,
354 which messes up the mtime of files opened with r+,
355 even if they're never actually written.
356 To avoid requiring special builds for clearcase users,
357 this is now the default. */
359 if ((fp
= fopen(fullname
, "r")) != NULL
) {
360 if(access(fullname
, W_OK
) != 0)
361 SET_PERM_LOCKED(window
->lockReasons
, TRUE
);
363 fp
= fopen(fullname
, "rb+");
365 /* Error opening file or file is not writeable */
366 fp
= fopen(fullname
, "rb");
368 /* File is read only */
369 SET_PERM_LOCKED(window
->lockReasons
, TRUE
);
371 } else if (flags
& CREATE
&& errno
== ENOENT
) {
372 /* Give option to create (or to exit if this is the only window) */
373 if (!(flags
& SUPPRESS_CREATE_WARN
)) {
374 /* on Solaris 2.6, and possibly other OSes, dialog won't
375 show if parent window is iconized. */
376 RaiseShellWindow(window
->shell
, False
);
378 /* ask user for next action if file not found */
379 if (WindowList
== window
&& window
->next
== NULL
) {
380 resp
= DialogF(DF_WARN
, window
->shell
, 3, "New File",
381 "Can't open %s:\n%s", "New File", "Cancel",
382 "Exit NEdit", fullname
, errorString());
385 resp
= DialogF(DF_WARN
, window
->shell
, 2, "New File",
386 "Can't open %s:\n%s", "New File", "Cancel", fullname
,
393 else if (resp
== 3) {
398 /* Test if new file can be created */
399 if ((fd
= creat(fullname
, 0666)) == -1) {
400 DialogF(DF_ERR
, window
->shell
, 1, "Error creating File",
401 "Can't create %s:\n%s", "OK", fullname
, errorString());
406 /* get correct version number and close before removing */
407 getname(fd
, fullname
);
413 SetWindowModified(window
, FALSE
);
414 if ((flags
& PREF_READ_ONLY
) != 0) {
415 SET_USER_LOCKED(window
->lockReasons
, TRUE
);
417 UpdateWindowReadOnly(window
);
422 DialogF(DF_ERR
, window
->shell
, 1, "Error opening File",
423 "Could not open %s%s:\n%s", "OK", path
, name
,
429 /* Get the length of the file, the protection mode, and the time of the
430 last modification to the file */
431 if (fstat(fileno(fp
), &statbuf
) != 0) {
433 window
->filenameSet
= FALSE
; /* Temp. prevent check for changes. */
434 DialogF(DF_ERR
, window
->shell
, 1, "Error opening File",
435 "Error opening %s", "OK", name
);
436 window
->filenameSet
= TRUE
;
440 if (S_ISDIR(statbuf
.st_mode
)) {
442 window
->filenameSet
= FALSE
; /* Temp. prevent check for changes. */
443 DialogF(DF_ERR
, window
->shell
, 1, "Error opening File",
444 "Can't open directory %s", "OK", name
);
445 window
->filenameSet
= TRUE
;
450 if (S_ISBLK(statbuf
.st_mode
)) {
452 window
->filenameSet
= FALSE
; /* Temp. prevent check for changes. */
453 DialogF(DF_ERR
, window
->shell
, 1, "Error opening File",
454 "Can't open block device %s", "OK", name
);
455 window
->filenameSet
= TRUE
;
459 fileLen
= statbuf
.st_size
;
461 /* Allocate space for the whole contents of the file (unfortunately) */
462 fileString
= (char *)malloc(fileLen
+1); /* +1 = space for null */
463 if (fileString
== NULL
) {
465 window
->filenameSet
= FALSE
; /* Temp. prevent check for changes. */
466 DialogF(DF_ERR
, window
->shell
, 1, "Error while opening File",
467 "File is too large to edit", "OK");
468 window
->filenameSet
= TRUE
;
472 /* Read the file into fileString and terminate with a null */
473 readLen
= fread(fileString
, sizeof(char), fileLen
, fp
);
476 window
->filenameSet
= FALSE
; /* Temp. prevent check for changes. */
477 DialogF(DF_ERR
, window
->shell
, 1, "Error while opening File",
478 "Error reading %s:\n%s", "OK", name
, errorString());
479 window
->filenameSet
= TRUE
;
483 fileString
[readLen
] = 0;
486 if (fclose(fp
) != 0) {
488 DialogF(DF_WARN
, window
->shell
, 1, "Error while opening File",
489 "Unable to close file", "OK");
490 /* we read it successfully, so continue */
493 /* Any errors that happen after this point leave the window in a
494 "broken" state, and thus RevertToSaved will abandon the window if
495 window->fileMissing is FALSE and doOpen fails. */
496 window
->fileMode
= statbuf
.st_mode
;
497 window
->lastModTime
= statbuf
.st_mtime
;
498 window
->fileMissing
= FALSE
;
499 /* Detect and convert DOS and Macintosh format files */
500 window
->fileFormat
= FormatOfFile(fileString
);
501 if (window
->fileFormat
== DOS_FILE_FORMAT
) {
502 ConvertFromDosFileString(fileString
, &readLen
, NULL
);
504 else if (window
->fileFormat
== MAC_FILE_FORMAT
) {
505 ConvertFromMacFileString(fileString
, readLen
);
508 /* Display the file contents in the text widget */
509 window
->ignoreModify
= True
;
510 BufSetAll(window
->buffer
, fileString
);
511 window
->ignoreModify
= False
;
513 /* Check that the length that the buffer thinks it has is the same
514 as what we gave it. If not, there were probably nuls in the file.
515 Substitute them with another character. If that is impossible, warn
516 the user, make the file read-only, and force a substitution */
517 if (window
->buffer
->length
!= readLen
) {
518 if (!BufSubstituteNullChars(fileString
, readLen
, window
->buffer
)) {
519 resp
= DialogF(DF_ERR
, window
->shell
, 2, "Error while opening File",
520 "Too much binary data in file. You may view\n"
521 "it, but not modify or re-save its contents.", "View",
527 SET_TMBD_LOCKED(window
->lockReasons
, TRUE
);
528 for (c
= fileString
; c
< &fileString
[readLen
]; c
++) {
533 window
->buffer
->nullSubsChar
= (char) 0xfe;
535 window
->ignoreModify
= True
;
536 BufSetAll(window
->buffer
, fileString
);
537 window
->ignoreModify
= False
;
540 /* Release the memory that holds fileString */
543 /* Set window title and file changed flag */
544 if ((flags
& PREF_READ_ONLY
) != 0) {
545 SET_USER_LOCKED(window
->lockReasons
, TRUE
);
547 if (IS_PERM_LOCKED(window
->lockReasons
)) {
548 window
->fileChanged
= FALSE
;
549 UpdateWindowTitle(window
);
551 SetWindowModified(window
, FALSE
);
552 if (IS_ANY_LOCKED(window
->lockReasons
)) {
553 UpdateWindowTitle(window
);
556 UpdateWindowReadOnly(window
);
561 int IncludeFile(WindowInfo
*window
, const char *name
)
564 int fileLen
, readLen
;
569 fp
= fopen(name
, "rb");
572 DialogF(DF_ERR
, window
->shell
, 1, "Error opening File",
573 "Could not open %s:\n%s", "OK", name
, errorString());
577 /* Get the length of the file */
578 if (fstat(fileno(fp
), &statbuf
) != 0)
580 DialogF(DF_ERR
, window
->shell
, 1, "Error opening File",
581 "Error opening %s", "OK", name
);
586 if (S_ISDIR(statbuf
.st_mode
))
588 DialogF(DF_ERR
, window
->shell
, 1, "Error opening File",
589 "Can't open directory %s", "OK", name
);
593 fileLen
= statbuf
.st_size
;
595 /* allocate space for the whole contents of the file */
596 fileString
= (char *)malloc(fileLen
+1); /* +1 = space for null */
597 if (fileString
== NULL
)
599 DialogF(DF_ERR
, window
->shell
, 1, "Error opening File",
600 "File is too large to include", "OK");
605 /* read the file into fileString and terminate with a null */
606 readLen
= fread(fileString
, sizeof(char), fileLen
, fp
);
609 DialogF(DF_ERR
, window
->shell
, 1, "Error opening File",
610 "Error reading %s:\n%s", "OK", name
, errorString());
615 fileString
[readLen
] = 0;
617 /* Detect and convert DOS and Macintosh format files */
618 switch (FormatOfFile(fileString
)) {
619 case DOS_FILE_FORMAT
:
620 ConvertFromDosFileString(fileString
, &readLen
, NULL
); break;
621 case MAC_FILE_FORMAT
:
622 ConvertFromMacFileString(fileString
, readLen
); break;
625 /* If the file contained ascii nulls, re-map them */
626 if (!BufSubstituteNullChars(fileString
, readLen
, window
->buffer
))
628 DialogF(DF_ERR
, window
->shell
, 1, "Error opening File",
629 "Too much binary data in file", "OK");
636 DialogF(DF_WARN
, window
->shell
, 1, "Error opening File",
637 "Unable to close file", "OK");
638 /* we read it successfully, so continue */
641 /* insert the contents of the file in the selection or at the insert
642 position in the window if no selection exists */
643 if (window
->buffer
->primary
.selected
)
644 BufReplaceSelected(window
->buffer
, fileString
);
646 BufInsert(window
->buffer
, TextGetCursorPos(window
->lastFocus
),
649 /* release the memory that holds fileString */
656 ** Close all files and windows, leaving one untitled window
658 int CloseAllFilesAndWindows(void)
660 while (WindowList
->next
!= NULL
||
661 WindowList
->filenameSet
|| WindowList
->fileChanged
) {
663 * When we're exiting through a macro, the document running the
664 * macro does not disappear from the list, so we could get stuck
665 * in an endless loop if we try to close it. Therefore, we close
666 * other documents first. (Note that the document running the macro
667 * may get closed because it is in the same window as another
668 * document that gets closed, but it won't disappear; it becomes
671 if (WindowList
== MacroRunWindow() && WindowList
->next
!= NULL
) {
672 if (!CloseAllDocumentInWindow(WindowList
->next
)) {
677 if (!CloseAllDocumentInWindow(WindowList
)) {
686 int CloseFileAndWindow(WindowInfo
*window
, int preResponse
)
690 /* Make sure that the window is not in iconified state */
691 if (window
->fileChanged
)
692 RaiseDocumentWindow(window
);
694 /* If the window is a normal & unmodified file or an empty new file,
695 or if the user wants to ignore external modifications then
696 just close it. Otherwise ask for confirmation first. */
697 if (!window
->fileChanged
&&
699 ((!window
->fileMissing
&& window
->lastModTime
> 0) ||
701 (window
->fileMissing
&& window
->lastModTime
== 0) ||
702 /* File deleted/modified externally, ignored by user. */
703 !GetPrefWarnFileMods()))
706 /* up-to-date windows don't have outstanding backup files to close */
709 if (preResponse
== PROMPT_SBC_DIALOG_RESPONSE
)
711 response
= DialogF(DF_WARN
, window
->shell
, 3, "Save File",
712 "Save %s before closing?", "Yes", "No", "Cancel", window
->filename
);
715 response
= preResponse
;
718 if (response
== YES_SBC_DIALOG_RESPONSE
)
721 stat
= SaveWindow(window
);
729 } else if (response
== NO_SBC_DIALOG_RESPONSE
)
732 RemoveBackupFile(window
);
734 } else /* 3 == Cancel */
742 int SaveWindow(WindowInfo
*window
)
746 /* Try to ensure our information is up-to-date */
747 CheckForChangesToFile(window
);
749 /* Return success if the file is normal & unchanged or is a
751 if ( (!window
->fileChanged
&& !window
->fileMissing
&&
752 window
->lastModTime
> 0) ||
753 IS_ANY_LOCKED_IGNORING_PERM(window
->lockReasons
))
755 /* Prompt for a filename if this is an Untitled window */
756 if (!window
->filenameSet
)
757 return SaveWindowAs(window
, NULL
, False
);
759 /* Check for external modifications and warn the user */
760 if (GetPrefWarnFileMods() && fileWasModifiedExternally(window
))
762 stat
= DialogF(DF_WARN
, window
->shell
, 2, "Save File",
763 "%s has been modified by another program.\n\n"
764 "Continuing this operation will overwrite any external\n"
765 "modifications to the file since it was opened in NEdit,\n"
766 "and your work or someone else's may potentially be lost.\n\n"
767 "To preserve the modified file, cancel this operation and\n"
768 "use Save As... to save this file under a different name,\n"
769 "or Revert to Saved to revert to the modified version.",
770 "Continue", "Cancel", window
->filename
);
773 /* Cancel and mark file as externally modified */
774 window
->lastModTime
= 0;
775 window
->fileMissing
= FALSE
;
781 RemoveBackupFile(window
);
782 stat
= doSave(window
);
784 if (writeBckVersion(window
))
786 stat
= doSave(window
);
788 RemoveBackupFile(window
);
793 int SaveWindowAs(WindowInfo
*window
, const char *newName
, int addWrap
)
795 int response
, retVal
, fileFormat
;
796 char fullname
[MAXPATHLEN
], filename
[MAXPATHLEN
], pathname
[MAXPATHLEN
];
797 WindowInfo
*otherWindow
;
799 /* Get the new name for the file */
800 if (newName
== NULL
) {
801 response
= PromptForNewFile(window
, "Save File As", fullname
,
802 &fileFormat
, &addWrap
);
803 if (response
!= GFN_OK
)
805 window
->fileFormat
= fileFormat
;
808 strcpy(fullname
, newName
);
811 if (1 == NormalizePathname(fullname
))
816 /* Add newlines if requested */
818 addWrapNewlines(window
);
820 if (ParseFilename(fullname
, filename
, pathname
) != 0) {
824 /* If the requested file is this file, just save it and return */
825 if (!strcmp(window
->filename
, filename
) &&
826 !strcmp(window
->path
, pathname
)) {
827 if (writeBckVersion(window
))
829 return doSave(window
);
832 /* If the file is open in another window, make user close it. Note that
833 it is possible for user to close the window by hand while the dialog
834 is still up, because the dialog is not application modal, so after
835 doing the dialog, check again whether the window still exists. */
836 otherWindow
= FindWindowWithFile(filename
, pathname
);
837 if (otherWindow
!= NULL
)
839 response
= DialogF(DF_WARN
, window
->shell
, 2, "File open",
840 "%s is open in another NEdit window", "Cancel",
841 "Close Other Window", filename
);
847 if (otherWindow
== FindWindowWithFile(filename
, pathname
))
849 if (!CloseFileAndWindow(otherWindow
, PROMPT_SBC_DIALOG_RESPONSE
))
856 /* Destroy the file closed property for the original file */
857 DeleteFileClosedProperty(window
);
859 /* Change the name of the file and save it under the new name */
860 RemoveBackupFile(window
);
861 strcpy(window
->filename
, filename
);
862 strcpy(window
->path
, pathname
);
863 window
->filenameSet
= TRUE
;
864 window
->fileMode
= 0;
865 CLEAR_ALL_LOCKS(window
->lockReasons
);
866 retVal
= doSave(window
);
867 UpdateWindowTitle(window
);
868 UpdateWindowReadOnly(window
);
869 RefreshTabState(window
);
871 /* Add the name to the convenience menu of previously opened files */
872 AddToPrevOpenMenu(fullname
);
874 /* If name has changed, language mode may have changed as well */
875 DetermineLanguageMode(window
, False
);
877 /* Update the stats line with the new filename */
878 UpdateStatsLine(window
);
884 static int doSave(WindowInfo
*window
)
886 char *fileString
= NULL
;
887 char fullname
[MAXPATHLEN
];
892 /* Get the full name of the file */
893 strcpy(fullname
, window
->path
);
894 strcat(fullname
, window
->filename
);
896 /* Check for root and warn him if he wants to write to a file with
897 none of the write bits set. */
899 && (0 == stat(fullname
, &statbuf
))
900 && !(statbuf
.st_mode
& (S_IWUSR
| S_IWGRP
| S_IWOTH
)))
902 result
= DialogF(DF_WARN
, window
->shell
, 2, "Writing Read-only File",
903 "File '%s' is marked as read-only.\n"
904 "Do you want to save anyway?",
905 "Save", "Cancel", window
->filename
);
913 /* strip the version number from the file so VMS will begin a new one */
914 removeVersionNumber(fullname
);
917 /* add a terminating newline if the file doesn't already have one for
918 Unix utilities which get confused otherwise
919 NOTE: this must be done _before_ we create/open the file, because the
920 (potential) buffer modification can trigger a check for file
921 changes. If the file is created for the first time, it has
922 zero size on disk, and the check would falsely conclude that the
923 file has changed on disk, and would pop up a warning dialog */
924 if (BufGetCharacter(window
->buffer
, window
->buffer
->length
- 1) != '\n'
925 && window
->buffer
->length
!= 0
926 && GetPrefAppendLF())
928 BufInsert(window
->buffer
, window
->buffer
->length
, "\n");
933 fp
= fopen(fullname
, "w", "rfm = stmlf");
935 fp
= fopen(fullname
, "wb");
939 result
= DialogF(DF_WARN
, window
->shell
, 2, "Error saving File",
940 "Unable to save %s:\n%s\n\nSave as a new file?",
941 "Save As...", "Cancel",
942 window
->filename
, errorString());
946 return SaveWindowAs(window
, NULL
, 0);
952 /* get the complete name of the file including the new version number */
953 fgetname(fp
, fullname
);
956 /* get the text buffer contents and its length */
957 fileString
= BufGetAll(window
->buffer
);
958 fileLen
= window
->buffer
->length
;
960 /* If null characters are substituted for, put them back */
961 BufUnsubstituteNullChars(fileString
, window
->buffer
);
963 /* If the file is to be saved in DOS or Macintosh format, reconvert */
964 if (window
->fileFormat
== DOS_FILE_FORMAT
)
966 if (!ConvertToDosFileString(&fileString
, &fileLen
))
968 DialogF(DF_ERR
, window
->shell
, 1, "Out of Memory",
969 "Out of memory! Try\nsaving in Unix format", "OK");
972 } else if (window
->fileFormat
== MAC_FILE_FORMAT
)
974 ConvertToMacFileString(fileString
, fileLen
);
977 /* write to the file */
978 #ifdef IBM_FWRITE_BUG
979 write(fileno(fp
), fileString
, fileLen
);
981 fwrite(fileString
, sizeof(char), fileLen
, fp
);
985 DialogF(DF_ERR
, window
->shell
, 1, "Error saving File",
986 "%s not saved:\n%s", "OK", window
->filename
, errorString());
996 DialogF(DF_ERR
, window
->shell
, 1, "Error closing File",
997 "Error closing file:\n%s", "OK", errorString());
1002 /* free the text buffer copy returned from XmTextGetString */
1006 /* reflect the fact that NEdit is now editing a new version of the file */
1007 ParseFilename(fullname
, window
->filename
, window
->path
);
1010 /* success, file was written */
1011 SetWindowModified(window
, FALSE
);
1013 /* update the modification time */
1014 if (stat(fullname
, &statbuf
) == 0) {
1015 window
->lastModTime
= statbuf
.st_mtime
;
1016 window
->fileMissing
= FALSE
;
1018 /* This needs to produce an error message -- the file can't be
1020 window
->lastModTime
= 0;
1021 window
->fileMissing
= TRUE
;
1028 ** Create a backup file for the current window. The name for the backup file
1029 ** is generated using the name and path stored in the window and adding a
1030 ** tilde (~) on UNIX and underscore (_) on VMS to the beginning of the name.
1032 int WriteBackupFile(WindowInfo
*window
)
1034 char *fileString
= NULL
;
1035 char name
[MAXPATHLEN
];
1039 /* Generate a name for the autoSave file */
1040 backupFileName(window
, name
, sizeof(name
));
1042 /* remove the old backup file.
1043 Well, this might fail - we'll notice later however. */
1046 /* open the file, set more restrictive permissions (using default
1047 permissions was somewhat of a security hole, because permissions were
1048 independent of those of the original file being edited */
1050 if ((fp
= fopen(name
, "w", "rfm = stmlf")) == NULL
)
1052 if ((fd
= open(name
, O_CREAT
|O_EXCL
|O_WRONLY
, S_IRUSR
| S_IWUSR
)) < 0
1053 || (fp
= fdopen(fd
, "w")) == NULL
)
1056 DialogF(DF_WARN
, window
->shell
, 1, "Error writing Backup",
1057 "Unable to save backup for %s:\n%s\n"
1058 "Automatic backup is now off", "OK", window
->filename
,
1060 window
->autoSave
= FALSE
;
1061 SetToggleButtonState(window
, window
->autoSaveItem
, FALSE
, FALSE
);
1065 /* Set VMS permissions */
1067 chmod(name
, S_IRUSR
| S_IWUSR
);
1070 /* get the text buffer contents and its length */
1071 fileString
= BufGetAll(window
->buffer
);
1072 fileLen
= window
->buffer
->length
;
1074 /* If null characters are substituted for, put them back */
1075 BufUnsubstituteNullChars(fileString
, window
->buffer
);
1077 /* add a terminating newline if the file doesn't already have one */
1078 if (fileLen
!= 0 && fileString
[fileLen
-1] != '\n')
1079 fileString
[fileLen
++] = '\n'; /* null terminator no longer needed */
1081 /* write out the file */
1082 #ifdef IBM_FWRITE_BUG
1083 write(fileno(fp
), fileString
, fileLen
);
1085 fwrite(fileString
, sizeof(char), fileLen
, fp
);
1089 DialogF(DF_ERR
, window
->shell
, 1, "Error saving Backup",
1090 "Error while saving backup for %s:\n%s\n"
1091 "Automatic backup is now off", "OK", window
->filename
,
1096 window
->autoSave
= FALSE
;
1100 /* close the backup file */
1101 if (fclose(fp
) != 0) {
1106 /* Free the text buffer copy returned from XmTextGetString */
1113 ** Remove the backup file associated with this window
1115 void RemoveBackupFile(WindowInfo
*window
)
1117 char name
[MAXPATHLEN
];
1119 /* Don't delete backup files when backups aren't activated. */
1120 if (window
->autoSave
== FALSE
)
1123 backupFileName(window
, name
, sizeof(name
));
1128 ** Generate the name of the backup file for this window from the filename
1129 ** and path in the window data structure & write into name
1131 static void backupFileName(WindowInfo
*window
, char *name
, int len
)
1133 char bckname
[MAXPATHLEN
];
1135 if (window
->filenameSet
)
1136 sprintf(name
, "%s_%s", window
->path
, window
->filename
);
1138 sprintf(name
, "%s_%s", "SYS$LOGIN:", window
->filename
);
1140 if (window
->filenameSet
)
1142 sprintf(name
, "%s~%s", window
->path
, window
->filename
);
1145 strcpy(bckname
, "~");
1146 strncat(bckname
, window
->filename
, MAXPATHLEN
- 1);
1147 PrependHome(bckname
, name
, len
);
1153 ** If saveOldVersion is on, copies the existing version of the file to
1154 ** <filename>.bck in anticipation of a new version being saved. Returns
1155 ** True if backup fails and user requests that the new file not be written.
1157 static int writeBckVersion(WindowInfo
*window
)
1160 char fullname
[MAXPATHLEN
], bckname
[MAXPATHLEN
];
1161 struct stat statbuf
;
1166 /* Do only if version backups are turned on */
1167 if (!window
->saveOldVersion
) {
1171 /* Get the full name of the file */
1172 strcpy(fullname
, window
->path
);
1173 strcat(fullname
, window
->filename
);
1175 /* Generate name for old version */
1176 if ((int)(strlen(fullname
) + 5) > (int)MAXPATHLEN
)
1178 return bckError(window
, "file name too long", window
->filename
);
1180 sprintf(bckname
, "%s.bck", fullname
);
1182 /* Delete the old backup file */
1183 /* Errors are ignored; we'll notice them later. */
1186 /* open the file being edited. If there are problems with the
1187 old file, don't bother the user, just skip the backup */
1188 inFP
= fopen(fullname
, "rb");
1193 /* find the length of the file */
1194 if (fstat(fileno(inFP
), &statbuf
) != 0) {
1197 fileLen
= statbuf
.st_size
;
1199 /* open the file exclusive and with restrictive permissions. */
1201 if ((outFP
= fopen(bckname
, "w", "rfm = stmlf")) == NULL
) {
1203 if ((fd
= open(bckname
, O_CREAT
|O_EXCL
|O_WRONLY
, S_IRUSR
| S_IWUSR
)) < 0
1204 || (outFP
= fdopen(fd
, "wb")) == NULL
) {
1207 return bckError(window
, "Error open backup file", bckname
);
1210 chmod(bckname
, S_IRUSR
| S_IWUSR
);
1213 /* Allocate space for the whole contents of the file */
1214 fileString
= (char *)malloc(fileLen
);
1215 if (fileString
== NULL
) {
1218 return bckError(window
, "out of memory", bckname
);
1221 /* read the file into fileString */
1222 fread(fileString
, sizeof(char), fileLen
, inFP
);
1230 /* close the input file, ignore any errors */
1233 /* write to the file */
1234 #ifdef IBM_FWRITE_BUG
1235 write(fileno(outFP
), fileString
, fileLen
);
1237 fwrite(fileString
, sizeof(char), fileLen
, outFP
);
1239 if (ferror(outFP
)) {
1243 return bckError(window
, errorString(), bckname
);
1247 /* close the file */
1248 if (fclose(outFP
) != 0)
1249 return bckError(window
, errorString(), bckname
);
1256 ** Error processing for writeBckVersion, gives the user option to cancel
1257 ** the subsequent save, or continue and optionally turn off versioning
1259 static int bckError(WindowInfo
*window
, const char *errString
, const char *file
)
1263 resp
= DialogF(DF_ERR
, window
->shell
, 3, "Error writing Backup",
1264 "Couldn't write .bck (last version) file.\n%s: %s", "Cancel Save",
1265 "Turn off Backups", "Continue", file
, errString
);
1269 window
->saveOldVersion
= FALSE
;
1271 SetToggleButtonState(window
, window
->saveLastItem
, FALSE
, FALSE
);
1277 void PrintWindow(WindowInfo
*window
, int selectedOnly
)
1279 textBuffer
*buf
= window
->buffer
;
1280 selection
*sel
= &buf
->primary
;
1281 char *fileString
= NULL
;
1284 /* get the contents of the text buffer from the text area widget. Add
1285 wrapping newlines if necessary to make it match the displayed text */
1287 if (!sel
->selected
) {
1288 XBell(TheDisplay
, 0);
1291 if (sel
->rectangular
) {
1292 fileString
= BufGetSelectionText(buf
);
1293 fileLen
= strlen(fileString
);
1295 fileString
= TextGetWrapped(window
->textArea
, sel
->start
, sel
->end
,
1298 fileString
= TextGetWrapped(window
->textArea
, 0, buf
->length
, &fileLen
);
1300 /* If null characters are substituted for, put them back */
1301 BufUnsubstituteNullChars(fileString
, buf
);
1303 /* add a terminating newline if the file doesn't already have one */
1304 if (fileLen
!= 0 && fileString
[fileLen
-1] != '\n')
1305 fileString
[fileLen
++] = '\n'; /* null terminator no longer needed */
1307 /* Print the string */
1308 PrintString(fileString
, fileLen
, window
->shell
, window
->filename
);
1310 /* Free the text buffer copy returned from XmTextGetString */
1315 ** Print a string (length is required). parent is the dialog parent, for
1316 ** error dialogs, and jobName is the print title.
1318 void PrintString(const char *string
, int length
, Widget parent
, const char *jobName
)
1320 char tmpFileName
[L_tmpnam
]; /* L_tmpnam defined in stdio.h */
1324 /* Generate a temporary file name */
1325 /* If the glibc is used, the linker issues a warning at this point. This is
1326 very thoughtful of him, but does not apply to NEdit. The recommended
1327 replacement mkstemp(3) uses the same algorithm as NEdit, namely
1328 1. Create a filename
1329 2. Open the file with the O_CREAT|O_EXCL flags
1330 So all an attacker can do is a DoS on the print function. */
1331 tmpnam(tmpFileName
);
1333 /* open the temporary file */
1335 if ((fp
= fopen(tmpFileName
, "w", "rfm = stmlf")) == NULL
)
1337 if ((fd
= open(tmpFileName
, O_CREAT
|O_EXCL
|O_WRONLY
, S_IRUSR
| S_IWUSR
)) < 0 || (fp
= fdopen(fd
, "w")) == NULL
)
1340 DialogF(DF_WARN
, parent
, 1, "Error while Printing",
1341 "Unable to write file for printing:\n%s", "OK",
1347 chmod(tmpFileName
, S_IRUSR
| S_IWUSR
);
1350 /* write to the file */
1351 #ifdef IBM_FWRITE_BUG
1352 write(fileno(fp
), string
, length
);
1354 fwrite(string
, sizeof(char), length
, fp
);
1358 DialogF(DF_ERR
, parent
, 1, "Error while Printing",
1359 "%s not printed:\n%s", "OK", jobName
, errorString());
1360 fclose(fp
); /* should call close(fd) in turn! */
1361 remove(tmpFileName
);
1365 /* close the temporary file */
1366 if (fclose(fp
) != 0)
1368 DialogF(DF_ERR
, parent
, 1, "Error while Printing",
1369 "Error closing temp. print file:\n%s", "OK",
1371 remove(tmpFileName
);
1375 /* Print the temporary file, then delete it and return success */
1377 strcat(tmpFileName
, ".");
1378 PrintFile(parent
, tmpFileName
, jobName
, True
);
1380 PrintFile(parent
, tmpFileName
, jobName
);
1381 remove(tmpFileName
);
1387 ** Wrapper for GetExistingFilename which uses the current window's path
1388 ** (if set) as the default directory.
1390 int PromptForExistingFile(WindowInfo
*window
, char *prompt
, char *fullname
)
1392 char *savedDefaultDir
;
1395 /* Temporarily set default directory to window->path, prompt for file,
1396 then, if the call was unsuccessful, restore the original default
1398 savedDefaultDir
= GetFileDialogDefaultDirectory();
1399 if (*window
->path
!= '\0')
1400 SetFileDialogDefaultDirectory(window
->path
);
1401 retVal
= GetExistingFilename(window
->shell
, prompt
, fullname
);
1402 if (retVal
!= GFN_OK
)
1403 SetFileDialogDefaultDirectory(savedDefaultDir
);
1404 if (savedDefaultDir
!= NULL
)
1405 XtFree(savedDefaultDir
);
1410 ** Wrapper for HandleCustomNewFileSB which uses the current window's path
1411 ** (if set) as the default directory, and asks about embedding newlines
1412 ** to make wrapping permanent.
1414 int PromptForNewFile(WindowInfo
*window
, char *prompt
, char *fullname
,
1415 int *fileFormat
, int *addWrap
)
1420 Widget fileSB
, wrapToggle
;
1421 Widget formatForm
, formatBtns
, unixFormat
, dosFormat
, macFormat
;
1422 char *savedDefaultDir
;
1424 *fileFormat
= window
->fileFormat
;
1426 /* Temporarily set default directory to window->path, prompt for file,
1427 then, if the call was unsuccessful, restore the original default
1429 savedDefaultDir
= GetFileDialogDefaultDirectory();
1430 if (*window
->path
!= '\0')
1431 SetFileDialogDefaultDirectory(window
->path
);
1433 /* Present a file selection dialog with an added field for requesting
1434 long line wrapping to become permanent via inserted newlines */
1437 XmNselectionLabelString
,
1438 s1
= XmStringCreateLocalized("New File Name:")); n
++;
1439 XtSetArg(args
[n
], XmNdialogStyle
, XmDIALOG_FULL_APPLICATION_MODAL
); n
++;
1442 s2
= XmStringCreateSimple(prompt
)); n
++;
1443 fileSB
= CreateFileSelectionDialog(window
->shell
,"FileSelect",args
,n
);
1446 formatForm
= XtVaCreateManagedWidget("formatForm", xmFormWidgetClass
,
1448 formatBtns
= XtVaCreateManagedWidget("formatBtns", xmRowColumnWidgetClass
,
1450 XmNradioBehavior
, XmONE_OF_MANY
,
1451 XmNorientation
, XmHORIZONTAL
,
1452 XmNpacking
, XmPACK_TIGHT
,
1453 XmNtopAttachment
, XmATTACH_FORM
,
1454 XmNleftAttachment
, XmATTACH_FORM
, NULL
);
1455 XtVaCreateManagedWidget("formatBtns", xmLabelWidgetClass
, formatBtns
,
1456 XmNlabelString
, s1
=XmStringCreateSimple("Format:"), NULL
);
1458 unixFormat
= XtVaCreateManagedWidget("unixFormat",
1459 xmToggleButtonWidgetClass
, formatBtns
, XmNlabelString
,
1460 s1
=XmStringCreateSimple("Unix"),
1461 XmNset
, *fileFormat
== UNIX_FILE_FORMAT
,
1462 XmNuserData
, UNIX_FILE_FORMAT
,
1463 XmNmarginHeight
, 0, XmNalignment
, XmALIGNMENT_BEGINNING
,
1464 XmNmnemonic
, 'U', NULL
);
1466 XtAddCallback(unixFormat
, XmNvalueChangedCallback
, setFormatCB
,
1468 dosFormat
= XtVaCreateManagedWidget("dosFormat",
1469 xmToggleButtonWidgetClass
, formatBtns
, XmNlabelString
,
1470 s1
=XmStringCreateSimple("DOS"),
1471 XmNset
, *fileFormat
== DOS_FILE_FORMAT
,
1472 XmNuserData
, DOS_FILE_FORMAT
,
1473 XmNmarginHeight
, 0, XmNalignment
, XmALIGNMENT_BEGINNING
,
1474 XmNmnemonic
, 'O', NULL
);
1476 XtAddCallback(dosFormat
, XmNvalueChangedCallback
, setFormatCB
,
1478 macFormat
= XtVaCreateManagedWidget("macFormat",
1479 xmToggleButtonWidgetClass
, formatBtns
, XmNlabelString
,
1480 s1
=XmStringCreateSimple("Macintosh"),
1481 XmNset
, *fileFormat
== MAC_FILE_FORMAT
,
1482 XmNuserData
, MAC_FILE_FORMAT
,
1483 XmNmarginHeight
, 0, XmNalignment
, XmALIGNMENT_BEGINNING
,
1484 XmNmnemonic
, 'M', NULL
);
1486 XtAddCallback(macFormat
, XmNvalueChangedCallback
, setFormatCB
,
1488 if (window
->wrapMode
== CONTINUOUS_WRAP
) {
1489 wrapToggle
= XtVaCreateManagedWidget("addWrap",
1490 xmToggleButtonWidgetClass
, formatForm
, XmNlabelString
,
1491 s1
=XmStringCreateSimple("Add line breaks where wrapped"),
1492 XmNalignment
, XmALIGNMENT_BEGINNING
,
1494 XmNtopAttachment
, XmATTACH_WIDGET
,
1495 XmNtopWidget
, formatBtns
,
1496 XmNleftAttachment
, XmATTACH_FORM
, NULL
);
1497 XtAddCallback(wrapToggle
, XmNvalueChangedCallback
, addWrapCB
,
1502 XtVaSetValues(XmFileSelectionBoxGetChild(fileSB
,
1503 XmDIALOG_FILTER_LABEL
), XmNmnemonic
, 'l', XmNuserData
,
1504 XmFileSelectionBoxGetChild(fileSB
, XmDIALOG_FILTER_TEXT
), NULL
);
1505 XtVaSetValues(XmFileSelectionBoxGetChild(fileSB
,
1506 XmDIALOG_DIR_LIST_LABEL
), XmNmnemonic
, 'D', XmNuserData
,
1507 XmFileSelectionBoxGetChild(fileSB
, XmDIALOG_DIR_LIST
), NULL
);
1508 XtVaSetValues(XmFileSelectionBoxGetChild(fileSB
,
1509 XmDIALOG_LIST_LABEL
), XmNmnemonic
, 'F', XmNuserData
,
1510 XmFileSelectionBoxGetChild(fileSB
, XmDIALOG_LIST
), NULL
);
1511 XtVaSetValues(XmFileSelectionBoxGetChild(fileSB
,
1512 XmDIALOG_SELECTION_LABEL
), XmNmnemonic
,
1513 prompt
[strspn(prompt
, "lFD")], XmNuserData
,
1514 XmFileSelectionBoxGetChild(fileSB
, XmDIALOG_TEXT
), NULL
);
1515 AddDialogMnemonicHandler(fileSB
, FALSE
);
1516 RemapDeleteKey(XmFileSelectionBoxGetChild(fileSB
, XmDIALOG_FILTER_TEXT
));
1517 RemapDeleteKey(XmFileSelectionBoxGetChild(fileSB
, XmDIALOG_TEXT
));
1518 retVal
= HandleCustomNewFileSB(fileSB
, fullname
,
1519 window
->filenameSet
? window
->filename
: NULL
);
1521 if (retVal
!= GFN_OK
)
1522 SetFileDialogDefaultDirectory(savedDefaultDir
);
1523 if (savedDefaultDir
!= NULL
)
1524 XtFree(savedDefaultDir
);
1529 ** Find a name for an untitled file, unique in the name space of in the opened
1530 ** files in this session, i.e. Untitled or Untitled_nn, and write it into
1531 ** the string "name".
1533 void UniqueUntitledName(char *name
)
1538 for (i
=0; i
<INT_MAX
; i
++) {
1540 sprintf(name
, "Untitled");
1542 sprintf(name
, "Untitled_%d", i
);
1543 for (w
=WindowList
; w
!=NULL
; w
=w
->next
)
1544 if (!strcmp(w
->filename
, name
))
1552 ** Check if the file in the window was changed by an external source.
1553 ** and put up a warning dialog if it has.
1555 void CheckForChangesToFile(WindowInfo
*window
)
1557 static WindowInfo
*lastCheckWindow
;
1558 static Time lastCheckTime
= 0;
1559 char fullname
[MAXPATHLEN
];
1560 struct stat statbuf
;
1563 int resp
, silent
= 0;
1564 XWindowAttributes winAttr
;
1566 if(!window
->filenameSet
)
1569 /* If last check was very recent, don't impact performance */
1570 timestamp
= XtLastTimestampProcessed(XtDisplay(window
->shell
));
1571 if (window
== lastCheckWindow
&&
1572 timestamp
- lastCheckTime
< MOD_CHECK_INTERVAL
)
1574 lastCheckWindow
= window
;
1575 lastCheckTime
= timestamp
;
1577 /* Update the status, but don't pop up a dialog if we're called
1578 from a place where the window might be iconic (e.g., from the
1579 replace dialog) or on another desktop.
1581 This works, but I bet it costs a round-trip to the server.
1582 Might be better to capture MapNotify/Unmap events instead.
1584 For tabs that are not on top, we don't want the dialog either,
1585 and we don't even need to contact the server to find out. By
1586 performing this check first, we avoid a server round-trip for
1587 most files in practice. */
1588 if (!IsTopDocument(window
))
1591 XGetWindowAttributes(XtDisplay(window
->shell
),
1592 XtWindow(window
->shell
),
1595 if (winAttr
.map_state
!= IsViewable
)
1599 /* Get the file mode and modification time */
1600 strcpy(fullname
, window
->path
);
1601 strcat(fullname
, window
->filename
);
1602 if (stat(fullname
, &statbuf
) != 0) {
1603 /* Return if we've already warned the user or we can't warn him now */
1604 if (window
->fileMissing
|| silent
)
1606 /* Can't stat the file -- maybe it's been deleted.
1607 The filename is now invalid */
1608 window
->fileMissing
= TRUE
;
1609 window
->lastModTime
= 1;
1610 /* Warn the user, if they like to be warned (Maybe this should be its
1611 own preference setting: GetPrefWarnFileDeleted() ) */
1612 if (GetPrefWarnFileMods()) {
1613 /* See note below about pop-up timing and XUngrabPointer */
1614 XUngrabPointer(XtDisplay(window
->shell
), timestamp
);
1615 if( errno
== EACCES
)
1616 resp
= 1 + DialogF(DF_ERR
, window
->shell
, 2,
1617 "File not Accessible",
1618 "You no longer have access to file \"%s\".\n"
1619 "Another program may have changed the permissions one of\n"
1620 "its parent directories.\nSave as a new file?",
1621 "Save As...", "Cancel", window
->filename
);
1623 resp
= DialogF(DF_ERR
, window
->shell
, 3, "File not found",
1624 "Error while checking the status of file \"%s\":\n"
1626 "Another program may have deleted or moved it.\n"
1627 "Re-Save file or Save as a new file?", "Re-Save",
1628 "Save As...", "Cancel", window
->filename
,
1633 SaveWindowAs(window
, NULL
, 0);
1635 /* A missing or (re-)saved file can't be read-only. */
1636 SET_PERM_LOCKED(window
->lockReasons
, False
);
1637 UpdateWindowTitle(window
);
1638 UpdateWindowReadOnly(window
);
1642 /* Check that the file's read-only status is still correct (but
1643 only if the file can still be opened successfully in read mode) */
1644 if (window
->fileMode
!= statbuf
.st_mode
) {
1645 window
->fileMode
= statbuf
.st_mode
;
1646 if ((fp
= fopen(fullname
, "r")) != NULL
) {
1649 #ifndef DONT_USE_ACCESS
1650 readOnly
= access(fullname
, W_OK
) != 0;
1652 if (((fp
= fopen(fullname
, "r+")) != NULL
)) {
1658 if (IS_PERM_LOCKED(window
->lockReasons
) != readOnly
) {
1659 SET_PERM_LOCKED(window
->lockReasons
, readOnly
);
1660 UpdateWindowTitle(window
);
1661 UpdateWindowReadOnly(window
);
1666 /* Warn the user if the file has been modified, unless checking is
1667 turned off or the user has already been warned. Popping up a dialog
1668 from a focus callback (which is how this routine is usually called)
1669 seems to catch Motif off guard, and if the timing is just right, the
1670 dialog can be left with a still active pointer grab from a Motif menu
1671 which is still in the process of popping down. The workaround, below,
1672 of calling XUngrabPointer is inelegant but seems to fix the problem. */
1674 ((window
->lastModTime
!= 0 &&
1675 window
->lastModTime
!= statbuf
.st_mtime
) ||
1676 window
->fileMissing
) ){
1677 window
->lastModTime
= 0; /* Inhibit further warnings */
1678 window
->fileMissing
= FALSE
;
1679 if (!GetPrefWarnFileMods())
1681 if (GetPrefWarnRealFileMods() &&
1682 !cmpWinAgainstFile(window
, fullname
)) {
1683 /* Contents hasn't changed. Update the modification time. */
1684 window
->lastModTime
= statbuf
.st_mtime
;
1687 XUngrabPointer(XtDisplay(window
->shell
), timestamp
);
1688 if (window
->fileChanged
)
1689 resp
= DialogF(DF_WARN
, window
->shell
, 2,
1690 "File modified externally",
1691 "%s has been modified by another program. Reload?\n\n"
1692 "WARNING: Reloading will discard changes made in this\n"
1693 "editing session!", "Reload", "Cancel", window
->filename
);
1695 resp
= DialogF(DF_WARN
, window
->shell
, 2,
1696 "File modified externally",
1697 "%s has been modified by another\nprogram. Reload?",
1698 "Reload", "Cancel", window
->filename
);
1700 RevertToSaved(window
);
1705 ** Return true if the file displayed in window has been modified externally
1706 ** to nedit. This should return FALSE if the file has been deleted or is
1709 static int fileWasModifiedExternally(WindowInfo
*window
)
1711 char fullname
[MAXPATHLEN
];
1712 struct stat statbuf
;
1714 if(!window
->filenameSet
)
1716 /* if (window->lastModTime == 0)
1718 strcpy(fullname
, window
->path
);
1719 strcat(fullname
, window
->filename
);
1720 if (stat(fullname
, &statbuf
) != 0)
1722 if (window
->lastModTime
== statbuf
.st_mtime
)
1724 if (GetPrefWarnRealFileMods() &&
1725 !cmpWinAgainstFile(window
, fullname
)) {
1732 ** Check the read-only or locked status of the window and beep and return
1733 ** false if the window should not be written in.
1735 int CheckReadOnly(WindowInfo
*window
)
1737 if (IS_ANY_LOCKED(window
->lockReasons
)) {
1738 XBell(TheDisplay
, 0);
1745 ** Wrapper for strerror so all the calls don't have to be ifdef'd for VMS.
1747 static const char *errorString(void)
1750 return strerror(errno
, vaxc$errno
);
1752 return strerror(errno
);
1758 ** Removing the VMS version number from a file name (if has one).
1760 void removeVersionNumber(char *fileName
)
1764 versionStart
= strrchr(fileName
, ';');
1765 if (versionStart
!= NULL
)
1766 *versionStart
= '\0';
1771 ** Callback procedure for File Format toggle buttons. Format is stored
1772 ** in userData field of widget button
1774 static void setFormatCB(Widget w
, XtPointer clientData
, XtPointer callData
)
1776 if (XmToggleButtonGetState(w
))
1777 XtVaGetValues(w
, XmNuserData
, clientData
, NULL
);
1781 ** Callback procedure for toggle button requesting newlines to be inserted
1782 ** to emulate continuous wrapping.
1784 static void addWrapCB(Widget w
, XtPointer clientData
, XtPointer callData
)
1787 int *addWrap
= (int *)clientData
;
1789 if (XmToggleButtonGetState(w
))
1791 resp
= DialogF(DF_WARN
, w
, 2, "Add Wrap",
1792 "This operation adds permanent line breaks to\n"
1793 "match the automatic wrapping done by the\n"
1794 "Continuous Wrap mode Preferences Option.\n\n"
1795 "*** This Option is Irreversable ***\n\n"
1796 "Once newlines are inserted, continuous wrapping\n"
1797 "will no longer work automatically on these lines", "OK",
1801 XmToggleButtonSetState(w
, False
, False
);
1814 ** Change a window created in NEdit's continuous wrap mode to the more
1815 ** conventional Unix format of embedded newlines. Indicate to the user
1816 ** by turning off Continuous Wrap mode.
1818 static void addWrapNewlines(WindowInfo
*window
)
1820 int fileLen
, i
, insertPositions
[MAX_PANES
], topLines
[MAX_PANES
];
1825 /* save the insert and scroll positions of each pane */
1826 for (i
=0; i
<=window
->nPanes
; i
++) {
1827 text
= i
==0 ? window
->textArea
: window
->textPanes
[i
-1];
1828 insertPositions
[i
] = TextGetCursorPos(text
);
1829 TextGetScroll(text
, &topLines
[i
], &horizOffset
);
1832 /* Modify the buffer to add wrapping */
1833 fileString
= TextGetWrapped(window
->textArea
, 0,
1834 window
->buffer
->length
, &fileLen
);
1835 BufSetAll(window
->buffer
, fileString
);
1838 /* restore the insert and scroll positions of each pane */
1839 for (i
=0; i
<=window
->nPanes
; i
++) {
1840 text
= i
==0 ? window
->textArea
: window
->textPanes
[i
-1];
1841 TextSetCursorPos(text
, insertPositions
[i
]);
1842 TextSetScroll(text
, topLines
[i
], 0);
1845 /* Show the user that something has happened by turning off
1846 Continuous Wrap mode */
1847 SetToggleButtonState(window
, window
->continuousWrapItem
, False
, True
);
1851 * Number of bytes read at once by cmpWinAgainstFile
1853 #define PREFERRED_CMPBUF_LEN 32768
1856 * Check if the contens of the textBuffer *buf is equal
1857 * the contens of the file named fileName. The format of
1858 * the file (UNIX/DOS/MAC) is handled properly.
1861 * 0: no difference found
1862 * !0: difference found or could not compare contents.
1864 static int cmpWinAgainstFile(WindowInfo
*window
, const char *fileName
)
1866 char fileString
[PREFERRED_CMPBUF_LEN
+ 2];
1867 struct stat statbuf
;
1868 int fileLen
, restLen
, nRead
, bufPos
, rv
, offset
, filePos
;
1870 int fileFormat
= window
->fileFormat
;
1871 char message
[MAXPATHLEN
+50];
1872 textBuffer
*buf
= window
->buffer
;
1875 fp
= fopen(fileName
, "r");
1878 if (fstat(fileno(fp
), &statbuf
) != 0) {
1883 fileLen
= statbuf
.st_size
;
1884 /* For DOS files, we can't simply check the length */
1885 if (fileFormat
!= DOS_FILE_FORMAT
) {
1886 if (fileLen
!= buf
->length
) {
1891 /* If a DOS file is smaller on disk, it's certainly different */
1892 if (fileLen
< buf
->length
) {
1898 /* For large files, the comparison can take a while. If it takes too long,
1899 the user should be given a clue about what is happening. */
1900 sprintf(message
, "Comparing externally modified %s ...", window
->filename
);
1901 restLen
= min(PREFERRED_CMPBUF_LEN
, fileLen
);
1904 while (restLen
> 0) {
1905 AllWindowsBusy(message
);
1907 fileString
[0] = pendingCR
;
1913 nRead
= fread(fileString
+offset
, sizeof(char), restLen
, fp
);
1914 if (nRead
!= restLen
) {
1923 if (fileFormat
== MAC_FILE_FORMAT
)
1924 ConvertFromMacFileString(fileString
, nRead
);
1925 else if (fileFormat
== DOS_FILE_FORMAT
)
1926 ConvertFromDosFileString(fileString
, &nRead
, &pendingCR
);
1928 /* Beware of 0 chars ! */
1929 BufSubstituteNullChars(fileString
, nRead
, buf
);
1930 rv
= BufCmp(buf
, bufPos
, nRead
, fileString
);
1937 restLen
= min(fileLen
- filePos
, PREFERRED_CMPBUF_LEN
);
1942 rv
= BufCmp(buf
, bufPos
, 1, &pendingCR
);
1948 if (bufPos
!= buf
->length
) {
1954 static int min(int i1
, int i2
)
1956 return i1
<= i2
? i1
: i2
;