Remove sprintf call - this won't compile on many platforms.
[nedit.git] / source / file.c
blobc455813d0cdc6bcd387f99d650bbf039b50b6ba7
1 static const char CVSID[] = "$Id: file.c,v 1.91 2004/10/18 15:54:11 yooden 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 <stdio.h>
52 #include <errno.h>
53 #include <limits.h>
54 #include <string.h>
55 #include <stdlib.h>
56 #ifdef VMS
57 #include "../util/VMSparam.h"
58 #include <types.h>
59 #include <stat.h>
60 #include <unixio.h>
61 #else
62 #include <sys/types.h>
63 #include <sys/stat.h>
64 #ifndef __MVS__
65 #include <sys/param.h>
66 #endif
67 #include <unistd.h>
68 #include <fcntl.h>
69 #endif /*VMS*/
71 #include <Xm/Xm.h>
72 #include <Xm/ToggleB.h>
73 #include <Xm/FileSB.h>
74 #include <Xm/RowColumn.h>
75 #include <Xm/Form.h>
76 #include <Xm/Label.h>
78 #ifdef HAVE_DEBUG_H
79 #include "../debug.h"
80 #endif
82 /* Maximum frequency in miliseconds of checking for external modifications.
83 The periodic check is only performed on buffer modification, and the check
84 interval is only to prevent checking on every keystroke in case of a file
85 system which is slow to process stat requests (which I'm not sure exists) */
86 #define MOD_CHECK_INTERVAL 3000
88 static int doSave(WindowInfo *window);
89 static void safeClose(WindowInfo *window);
90 static int doOpen(WindowInfo *window, const char *name, const char *path,
91 int flags);
92 static void backupFileName(WindowInfo *window, char *name, int len);
93 static int writeBckVersion(WindowInfo *window);
94 static int bckError(WindowInfo *window, const char *errString, const char *file);
95 static int fileWasModifiedExternally(WindowInfo *window);
96 static const char *errorString(void);
97 static void addWrapNewlines(WindowInfo *window);
98 static void setFormatCB(Widget w, XtPointer clientData, XtPointer callData);
99 static void addWrapCB(Widget w, XtPointer clientData, XtPointer callData);
100 static int cmpWinAgainstFile(WindowInfo *window, const char *fileName);
101 static int min(int i1, int i2);
103 #ifdef VMS
104 void removeVersionNumber(char *fileName);
105 #endif /*VMS*/
107 WindowInfo *EditNewFile(WindowInfo *inWindow, char *geometry, int iconic,
108 const char *languageMode, const char *defaultPath)
110 char name[MAXPATHLEN];
111 WindowInfo *window;
113 /*... test for creatability? */
115 /* Find a (relatively) unique name for the new file */
116 UniqueUntitledName(name);
118 /* create new window/document */
119 if (inWindow)
120 window = CreateDocument(inWindow, name, geometry, iconic);
121 else
122 window = CreateWindow(name, geometry, iconic);
124 strcpy(window->filename, name);
125 strcpy(window->path, defaultPath ? defaultPath : "");
126 SetWindowModified(window, FALSE);
127 CLEAR_ALL_LOCKS(window->lockReasons);
128 UpdateWindowReadOnly(window);
129 UpdateStatsLine(window);
130 UpdateWindowTitle(window);
131 RefreshTabState(window);
133 if (languageMode == NULL)
134 DetermineLanguageMode(window, True);
135 else
136 SetLanguageMode(window, FindLanguageMode(languageMode), True);
138 ShowTabBar(window, GetShowTabBar(window));
140 if (iconic && IsIconic(window))
141 RaiseDocument(window);
142 else
143 RaiseDocumentWindow(window);
145 SortTabBar(window);
146 return window;
150 ** Open an existing file specified by name and path. Use the window inWindow
151 ** unless inWindow is NULL or points to a window which is already in use
152 ** (displays a file other than Untitled, or is Untitled but modified). Flags
153 ** can be any of:
155 ** CREATE: If file is not found, (optionally) prompt the
156 ** user whether to create
157 ** SUPPRESS_CREATE_WARN When creating a file, don't ask the user
158 ** PREF_READ_ONLY Make the file read-only regardless
160 ** If languageMode is passed as NULL, it will be determined automatically
161 ** from the file extension or file contents.
163 ** If bgOpen is True, then the file will be open in background. This
164 ** works in association with the SetLanguageMode() function that has
165 ** the syntax highlighting deferred, in order to speed up the file-
166 ** opening operation when multiple files are being opened in succession.
168 WindowInfo *EditExistingFile(WindowInfo *inWindow, const char *name,
169 const char *path, int flags, char *geometry, int iconic,
170 const char *languageMode, int tabbed, int bgOpen)
172 WindowInfo *window;
173 char fullname[MAXPATHLEN];
175 /* first look to see if file is already displayed in a window */
176 window = FindWindowWithFile(name, path);
177 if (window != NULL) {
178 if (!bgOpen) {
179 if (iconic)
180 RaiseDocument(window);
181 else
182 RaiseDocumentWindow(window);
184 return window;
187 /* If an existing window isn't specified; or the window is already
188 in use (not Untitled or Untitled and modified), or is currently
189 busy running a macro; create the window */
190 if (inWindow == NULL) {
191 window = CreateWindow(name, geometry, iconic);
193 else if (inWindow->filenameSet || inWindow->fileChanged ||
194 inWindow->macroCmdData != NULL) {
195 if (tabbed) {
196 window = CreateDocument(inWindow, name, geometry, iconic);
198 else {
199 window = CreateWindow(name, geometry, iconic);
202 else {
203 /* open file in untitled document */
204 window = inWindow;
205 strcpy(window->path, path);
206 strcpy(window->filename, name);
207 if (!iconic && !bgOpen) {
208 RaiseDocumentWindow(window);
212 /* Open the file */
213 if (!doOpen(window, name, path, flags)) {
214 /* The user may have destroyed the window instead of closing the
215 warning dialog; don't close it twice */
216 safeClose(window);
218 return NULL;
221 /* Decide what language mode to use, trigger language specific actions */
222 if (languageMode == NULL)
223 DetermineLanguageMode(window, True);
224 else
225 SetLanguageMode(window, FindLanguageMode(languageMode), True);
227 /* update tab label and tooltip */
228 RefreshTabState(window);
229 SortTabBar(window);
230 ShowTabBar(window, GetShowTabBar(window));
232 if (!bgOpen)
233 RaiseDocument(window);
235 /* Bring the title bar and statistics line up to date, doOpen does
236 not necessarily set the window title or read-only status */
237 UpdateWindowTitle(window);
238 UpdateWindowReadOnly(window);
239 UpdateStatsLine(window);
241 /* Add the name to the convenience menu of previously opened files */
242 strcpy(fullname, path);
243 strcat(fullname, name);
244 if(GetPrefAlwaysCheckRelTagsSpecs())
245 AddRelTagsFile(GetPrefTagFile(), path, TAG);
246 AddToPrevOpenMenu(fullname);
248 return window;
251 void RevertToSaved(WindowInfo *window)
253 char name[MAXPATHLEN], path[MAXPATHLEN];
254 int i;
255 int insertPositions[MAX_PANES], topLines[MAX_PANES];
256 int horizOffsets[MAX_PANES];
257 int openFlags = 0;
258 Widget text;
260 /* Can't revert untitled windows */
261 if (!window->filenameSet)
263 DialogF(DF_WARN, window->shell, 1, "Error",
264 "Window '%s' was never saved, can't re-read", "OK",
265 window->filename);
266 return;
269 /* save insert & scroll positions of all of the panes to restore later */
270 for (i=0; i<=window->nPanes; i++) {
271 text = i==0 ? window->textArea : window->textPanes[i-1];
272 insertPositions[i] = TextGetCursorPos(text);
273 TextGetScroll(text, &topLines[i], &horizOffsets[i]);
276 /* re-read the file, update the window title if new file is different */
277 strcpy(name, window->filename);
278 strcpy(path, window->path);
279 RemoveBackupFile(window);
280 ClearUndoList(window);
281 openFlags |= IS_USER_LOCKED(window->lockReasons) ? PREF_READ_ONLY : 0;
282 if (!doOpen(window, name, path, openFlags)) {
283 /* This is a bit sketchy. The only error in doOpen that irreperably
284 damages the window is "too much binary data". It should be
285 pretty rare to be reverting something that was fine only to find
286 that now it has too much binary data. */
287 if (!window->fileMissing)
288 safeClose(window);
289 else {
290 /* Treat it like an externally modified file */
291 window->lastModTime=0;
292 window->fileMissing=FALSE;
294 return;
296 UpdateWindowTitle(window);
297 UpdateWindowReadOnly(window);
299 /* restore the insert and scroll positions of each pane */
300 for (i=0; i<=window->nPanes; i++) {
301 text = i==0 ? window->textArea : window->textPanes[i-1];
302 TextSetCursorPos(text, insertPositions[i]);
303 TextSetScroll(text, topLines[i], horizOffsets[i]);
308 ** Checks whether a window is still alive, and closes it only if so.
309 ** Intended to be used when the file could not be opened for some reason.
310 ** Normally the window is still alive, but the user may have closed the
311 ** window instead of the error dialog. In that case, we shouldn't close the
312 ** window a second time.
314 static void safeClose(WindowInfo *window)
316 WindowInfo* p = WindowList;
317 while(p) {
318 if (p == window) {
319 CloseWindow(window);
320 return;
322 p = p->next;
326 static int doOpen(WindowInfo *window, const char *name, const char *path,
327 int flags)
329 char fullname[MAXPATHLEN];
330 struct stat statbuf;
331 int fileLen, readLen;
332 char *fileString, *c;
333 FILE *fp = NULL;
334 int fd;
335 int resp;
337 /* initialize lock reasons */
338 CLEAR_ALL_LOCKS(window->lockReasons);
340 /* Update the window data structure */
341 strcpy(window->filename, name);
342 strcpy(window->path, path);
343 window->filenameSet = TRUE;
344 window->fileMissing = TRUE;
346 /* Get the full name of the file */
347 strcpy(fullname, path);
348 strcat(fullname, name);
350 /* Open the file */
351 #ifndef DONT_USE_ACCESS
352 /* The only advantage of this is if you use clearcase,
353 which messes up the mtime of files opened with r+,
354 even if they're never actually written.
355 To avoid requiring special builds for clearcase users,
356 this is now the default. */
358 if ((fp = fopen(fullname, "r")) != NULL) {
359 if(access(fullname, W_OK) != 0)
360 SET_PERM_LOCKED(window->lockReasons, TRUE);
361 #else
362 fp = fopen(fullname, "rb+");
363 if (fp == NULL) {
364 /* Error opening file or file is not writeable */
365 fp = fopen(fullname, "rb");
366 if (fp != NULL) {
367 /* File is read only */
368 SET_PERM_LOCKED(window->lockReasons, TRUE);
369 #endif
370 } else if (flags & CREATE && errno == ENOENT) {
371 /* Give option to create (or to exit if this is the only window) */
372 if (!(flags & SUPPRESS_CREATE_WARN))
374 if (WindowList == window && window->next == NULL)
376 resp = DialogF(DF_WARN, window->shell, 3, "New File",
377 "Can't open %s:\n%s", "New File", "Cancel",
378 "Exit NEdit", fullname, errorString());
379 } else
381 resp = DialogF(DF_WARN, window->shell, 2, "New File",
382 "Can't open %s:\n%s", "New File", "Cancel", fullname,
383 errorString());
386 if (resp == 2)
388 return FALSE;
389 } else if (resp == 3)
391 exit(EXIT_SUCCESS);
395 /* Test if new file can be created */
396 if ((fd = creat(fullname, 0666)) == -1)
398 DialogF(DF_ERR, window->shell, 1, "Error creating File",
399 "Can't create %s:\n%s", "OK", fullname, errorString());
400 return FALSE;
401 } else
403 #ifdef VMS
404 /* get correct version number and close before removing */
405 getname(fd, fullname);
406 #endif
407 close(fd);
408 remove(fullname);
411 SetWindowModified(window, FALSE);
412 if ((flags & PREF_READ_ONLY) != 0) {
413 SET_USER_LOCKED(window->lockReasons, TRUE);
415 UpdateWindowReadOnly(window);
416 return TRUE;
417 } else
419 /* A true error */
420 DialogF(DF_ERR, window->shell, 1, "Error opening File",
421 "Could not open %s%s:\n%s", "OK", path, name,
422 errorString());
423 return FALSE;
427 /* Get the length of the file, the protection mode, and the time of the
428 last modification to the file */
429 if (fstat(fileno(fp), &statbuf) != 0)
431 fclose(fp);
432 window->filenameSet = FALSE; /* Temp. prevent check for changes. */
433 DialogF(DF_ERR, window->shell, 1, "Error opening File",
434 "Error opening %s", "OK", name);
435 window->filenameSet = TRUE;
436 return FALSE;
439 if (S_ISDIR(statbuf.st_mode))
441 fclose(fp);
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;
446 return FALSE;
449 #ifdef S_ISBLK
450 if (S_ISBLK(statbuf.st_mode))
452 fclose(fp);
453 window->filenameSet = FALSE; /* Temp. prevent check for changes. */
454 DialogF(DF_ERR, window->shell, 1, "Error opening File",
455 "Can't open block device %s", "OK", name);
456 window->filenameSet = TRUE;
457 return FALSE;
459 #endif
460 fileLen = statbuf.st_size;
462 /* Allocate space for the whole contents of the file (unfortunately) */
463 fileString = (char *)malloc(fileLen+1); /* +1 = space for null */
464 if (fileString == NULL)
466 fclose(fp);
467 window->filenameSet = FALSE; /* Temp. prevent check for changes. */
468 DialogF(DF_ERR, window->shell, 1, "Error while opening File",
469 "File is too large to edit", "OK");
470 window->filenameSet = TRUE;
471 return FALSE;
474 /* Read the file into fileString and terminate with a null */
475 readLen = fread(fileString, sizeof(char), fileLen, fp);
476 if (ferror(fp))
478 fclose(fp);
479 window->filenameSet = FALSE; /* Temp. prevent check for changes. */
480 DialogF(DF_ERR, window->shell, 1, "Error while opening File",
481 "Error reading %s:\n%s", "OK", name, errorString());
482 window->filenameSet = TRUE;
483 free(fileString);
484 return FALSE;
486 fileString[readLen] = 0;
488 /* Close the file */
489 if (fclose(fp) != 0)
491 /* unlikely error */
492 DialogF(DF_WARN, window->shell, 1, "Error while opening File",
493 "Unable to close file", "OK");
494 /* we read it successfully, so continue */
497 /* Any errors that happen after this point leave the window in a
498 "broken" state, and thus RevertToSaved will abandon the window if
499 window->fileMissing is FALSE and doOpen fails. */
500 window->fileMode = statbuf.st_mode;
501 window->lastModTime = statbuf.st_mtime;
502 window->fileMissing = FALSE;
503 /* Detect and convert DOS and Macintosh format files */
504 window->fileFormat = FormatOfFile(fileString);
505 if (window->fileFormat == DOS_FILE_FORMAT)
506 ConvertFromDosFileString(fileString, &readLen, NULL);
507 else if (window->fileFormat == MAC_FILE_FORMAT)
508 ConvertFromMacFileString(fileString, readLen);
510 /* Display the file contents in the text widget */
511 window->ignoreModify = True;
512 BufSetAll(window->buffer, fileString);
513 window->ignoreModify = False;
515 /* Check that the length that the buffer thinks it has is the same
516 as what we gave it. If not, there were probably nuls in the file.
517 Substitute them with another character. If that is impossible, warn
518 the user, make the file read-only, and force a substitution */
519 if (window->buffer->length != readLen)
521 if (!BufSubstituteNullChars(fileString, readLen, window->buffer))
523 resp = DialogF(DF_ERR, window->shell, 2, "Error while opening File",
524 "Too much binary data in file. You may view\n"
525 "it, but not modify or re-save its contents.", "View",
526 "Cancel");
527 if (resp == 2)
529 return FALSE;
532 SET_TMBD_LOCKED(window->lockReasons, TRUE);
533 for (c = fileString; c < &fileString[readLen]; c++)
535 if (*c == '\0')
537 *c = (char) 0xfe;
540 window->buffer->nullSubsChar = (char) 0xfe;
542 window->ignoreModify = True;
543 BufSetAll(window->buffer, fileString);
544 window->ignoreModify = False;
547 /* Release the memory that holds fileString */
548 free(fileString);
550 /* Set window title and file changed flag */
551 if ((flags & PREF_READ_ONLY) != 0) {
552 SET_USER_LOCKED(window->lockReasons, TRUE);
554 if (IS_PERM_LOCKED(window->lockReasons)) {
555 window->fileChanged = FALSE;
556 UpdateWindowTitle(window);
557 } else {
558 SetWindowModified(window, FALSE);
559 if (IS_ANY_LOCKED(window->lockReasons)) {
560 UpdateWindowTitle(window);
563 UpdateWindowReadOnly(window);
565 return TRUE;
568 int IncludeFile(WindowInfo *window, const char *name)
570 struct stat statbuf;
571 int fileLen, readLen;
572 char *fileString;
573 FILE *fp = NULL;
575 /* Open the file */
576 fp = fopen(name, "rb");
577 if (fp == NULL)
579 DialogF(DF_ERR, window->shell, 1, "Error opening File",
580 "Could not open %s:\n%s", "OK", name, errorString());
581 return FALSE;
584 /* Get the length of the file */
585 if (fstat(fileno(fp), &statbuf) != 0)
587 DialogF(DF_ERR, window->shell, 1, "Error opening File",
588 "Error opening %s", "OK", name);
589 fclose(fp);
590 return FALSE;
593 if (S_ISDIR(statbuf.st_mode))
595 DialogF(DF_ERR, window->shell, 1, "Error opening File",
596 "Can't open directory %s", "OK", name);
597 fclose(fp);
598 return FALSE;
600 fileLen = statbuf.st_size;
602 /* allocate space for the whole contents of the file */
603 fileString = (char *)malloc(fileLen+1); /* +1 = space for null */
604 if (fileString == NULL)
606 DialogF(DF_ERR, window->shell, 1, "Error opening File",
607 "File is too large to include", "OK");
608 fclose(fp);
609 return FALSE;
612 /* read the file into fileString and terminate with a null */
613 readLen = fread(fileString, sizeof(char), fileLen, fp);
614 if (ferror(fp))
616 DialogF(DF_ERR, window->shell, 1, "Error opening File",
617 "Error reading %s:\n%s", "OK", name, errorString());
618 fclose(fp);
619 free(fileString);
620 return FALSE;
622 fileString[readLen] = 0;
624 /* Detect and convert DOS and Macintosh format files */
625 switch (FormatOfFile(fileString)) {
626 case DOS_FILE_FORMAT:
627 ConvertFromDosFileString(fileString, &readLen, NULL); break;
628 case MAC_FILE_FORMAT:
629 ConvertFromMacFileString(fileString, readLen); break;
632 /* If the file contained ascii nulls, re-map them */
633 if (!BufSubstituteNullChars(fileString, readLen, window->buffer))
635 DialogF(DF_ERR, window->shell, 1, "Error opening File",
636 "Too much binary data in file", "OK");
639 /* close the file */
640 if (fclose(fp) != 0)
642 /* unlikely error */
643 DialogF(DF_WARN, window->shell, 1, "Error opening File",
644 "Unable to close file", "OK");
645 /* we read it successfully, so continue */
648 /* insert the contents of the file in the selection or at the insert
649 position in the window if no selection exists */
650 if (window->buffer->primary.selected)
651 BufReplaceSelected(window->buffer, fileString);
652 else
653 BufInsert(window->buffer, TextGetCursorPos(window->lastFocus),
654 fileString);
656 /* release the memory that holds fileString */
657 free(fileString);
659 return TRUE;
663 ** Close all files and windows, leaving one untitled window
665 int CloseAllFilesAndWindows(void)
667 while (WindowList->next != NULL ||
668 WindowList->filenameSet || WindowList->fileChanged) {
670 * When we're exiting through a macro, the document running the
671 * macro does not disappear from the list, so we could get stuck
672 * in an endless loop if we try to close it. Therefore, we close
673 * other documents first. (Note that the document running the macro
674 * may get closed because it is in the same window as another
675 * document that gets closed, but it won't disappear; it becomes
676 * Untitled.)
678 if (WindowList == MacroRunWindow() && WindowList->next != NULL) {
679 if (!CloseAllDocumentInWindow(WindowList->next)) {
680 return False;
683 else {
684 if (!CloseAllDocumentInWindow(WindowList)) {
685 return False;
690 return TRUE;
693 int CloseFileAndWindow(WindowInfo *window, int preResponse)
695 int response, stat;
697 /* Make sure that the window is not in iconified state */
698 if (window->fileChanged)
699 RaiseDocumentWindow(window);
701 /* If the window is a normal & unmodified file or an empty new file,
702 or if the user wants to ignore external modifications then
703 just close it. Otherwise ask for confirmation first. */
704 if (!window->fileChanged &&
705 /* Normal File */
706 ((!window->fileMissing && window->lastModTime > 0) ||
707 /* New File*/
708 (window->fileMissing && window->lastModTime == 0) ||
709 /* File deleted/modified externally, ignored by user. */
710 !GetPrefWarnFileMods()))
712 CloseWindow(window);
713 /* up-to-date windows don't have outstanding backup files to close */
714 } else
716 if (preResponse == PROMPT_SBC_DIALOG_RESPONSE)
718 response = DialogF(DF_WARN, window->shell, 3, "Save File",
719 "Save %s before closing?", "Yes", "No", "Cancel", window->filename);
720 } else
722 response = preResponse;
725 if (response == YES_SBC_DIALOG_RESPONSE)
727 /* Save */
728 stat = SaveWindow(window);
729 if (stat)
731 CloseWindow(window);
732 } else
734 return FALSE;
736 } else if (response == NO_SBC_DIALOG_RESPONSE)
738 /* Don't Save */
739 RemoveBackupFile(window);
740 CloseWindow(window);
741 } else /* 3 == Cancel */
743 return FALSE;
746 return TRUE;
749 int SaveWindow(WindowInfo *window)
751 int stat;
753 /* Try to ensure our information is up-to-date */
754 CheckForChangesToFile(window);
756 /* Return success if the file is normal & unchanged or is a
757 read-only file. */
758 if ( (!window->fileChanged && !window->fileMissing &&
759 window->lastModTime > 0) ||
760 IS_ANY_LOCKED_IGNORING_PERM(window->lockReasons))
761 return TRUE;
762 /* Prompt for a filename if this is an Untitled window */
763 if (!window->filenameSet)
764 return SaveWindowAs(window, NULL, False);
766 /* Check for external modifications and warn the user */
767 if (GetPrefWarnFileMods() && fileWasModifiedExternally(window))
769 stat = DialogF(DF_WARN, window->shell, 2, "Save File",
770 "%s has been modified by another program.\n\n"
771 "Continuing this operation will overwrite any external\n"
772 "modifications to the file since it was opened in NEdit,\n"
773 "and your work or someone else's may potentially be lost.\n\n"
774 "To preserve the modified file, cancel this operation and\n"
775 "use Save As... to save this file under a different name,\n"
776 "or Revert to Saved to revert to the modified version.",
777 "Continue", "Cancel", window->filename);
778 if (stat == 2)
780 /* Cancel and mark file as externally modified */
781 window->lastModTime = 0;
782 window->fileMissing = FALSE;
783 return FALSE;
787 #ifdef VMS
788 RemoveBackupFile(window);
789 stat = doSave(window);
790 #else
791 if (writeBckVersion(window))
792 return FALSE;
793 stat = doSave(window);
794 if (stat)
795 RemoveBackupFile(window);
796 #endif /*VMS*/
797 return stat;
800 int SaveWindowAs(WindowInfo *window, const char *newName, int addWrap)
802 int response, retVal, fileFormat;
803 char fullname[MAXPATHLEN], filename[MAXPATHLEN], pathname[MAXPATHLEN];
804 WindowInfo *otherWindow;
806 /* Get the new name for the file */
807 if (newName == NULL) {
808 response = PromptForNewFile(window, "Save File As", fullname,
809 &fileFormat, &addWrap);
810 if (response != GFN_OK)
811 return FALSE;
812 window->fileFormat = fileFormat;
813 } else
815 strcpy(fullname, newName);
818 if (1 == NormalizePathname(fullname))
820 return False;
823 /* Add newlines if requested */
824 if (addWrap)
825 addWrapNewlines(window);
827 if (ParseFilename(fullname, filename, pathname) != 0) {
828 return FALSE;
831 /* If the requested file is this file, just save it and return */
832 if (!strcmp(window->filename, filename) &&
833 !strcmp(window->path, pathname)) {
834 if (writeBckVersion(window))
835 return FALSE;
836 return doSave(window);
839 /* If the file is open in another window, make user close it. Note that
840 it is possible for user to close the window by hand while the dialog
841 is still up, because the dialog is not application modal, so after
842 doing the dialog, check again whether the window still exists. */
843 otherWindow = FindWindowWithFile(filename, pathname);
844 if (otherWindow != NULL)
846 response = DialogF(DF_WARN, window->shell, 2, "File open",
847 "%s is open in another NEdit window", "Cancel",
848 "Close Other Window", filename);
850 if (response == 1)
852 return FALSE;
854 if (otherWindow == FindWindowWithFile(filename, pathname))
856 if (!CloseFileAndWindow(otherWindow, PROMPT_SBC_DIALOG_RESPONSE))
858 return FALSE;
863 /* Destroy the file closed property for the original file */
864 DeleteFileClosedProperty(window);
866 /* Change the name of the file and save it under the new name */
867 RemoveBackupFile(window);
868 strcpy(window->filename, filename);
869 strcpy(window->path, pathname);
870 window->filenameSet = TRUE;
871 window->fileMode = 0;
872 CLEAR_ALL_LOCKS(window->lockReasons);
873 retVal = doSave(window);
874 UpdateWindowTitle(window);
875 UpdateWindowReadOnly(window);
876 RefreshTabState(window);
878 /* Add the name to the convenience menu of previously opened files */
879 AddToPrevOpenMenu(fullname);
881 /* If name has changed, language mode may have changed as well */
882 DetermineLanguageMode(window, False);
884 /* Update the stats line with the new filename */
885 UpdateStatsLine(window);
887 SortTabBar(window);
888 return retVal;
891 static int doSave(WindowInfo *window)
893 char *fileString = NULL;
894 char fullname[MAXPATHLEN];
895 struct stat statbuf;
896 FILE *fp;
897 int fileLen, result;
899 /* Get the full name of the file */
900 strcpy(fullname, window->path);
901 strcat(fullname, window->filename);
903 /* Check for root and warn him if he wants to write to a file with
904 none of the write bits set. */
905 if ((0 == getuid())
906 && (0 == stat(fullname, &statbuf))
907 && !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH)))
909 result = DialogF(DF_WARN, window->shell, 2, "Writing Read-only File",
910 "File '%s' is marked as read-only.\n"
911 "Do you want to save anyway?",
912 "Save", "Cancel", window->filename);
913 if (1 != result)
915 return True;
919 #ifdef VMS
920 /* strip the version number from the file so VMS will begin a new one */
921 removeVersionNumber(fullname);
922 #endif
924 /* add a terminating newline if the file doesn't already have one for
925 Unix utilities which get confused otherwise
926 NOTE: this must be done _before_ we create/open the file, because the
927 (potential) buffer modification can trigger a check for file
928 changes. If the file is created for the first time, it has
929 zero size on disk, and the check would falsely conclude that the
930 file has changed on disk, and would pop up a warning dialog */
931 if (BufGetCharacter(window->buffer, window->buffer->length - 1) != '\n'
932 && window->buffer->length != 0
933 && GetPrefAppendLF())
935 BufInsert(window->buffer, window->buffer->length, "\n");
938 /* open the file */
939 #ifdef VMS
940 fp = fopen(fullname, "w", "rfm = stmlf");
941 #else
942 fp = fopen(fullname, "wb");
943 #endif /* VMS */
944 if (fp == NULL)
946 result = DialogF(DF_WARN, window->shell, 2, "Error saving File",
947 "Unable to save %s:\n%s\n\nSave as a new file?",
948 "Save As...", "Cancel",
949 window->filename, errorString());
951 if (result == 1)
953 return SaveWindowAs(window, NULL, 0);
955 return FALSE;
958 #ifdef VMS
959 /* get the complete name of the file including the new version number */
960 fgetname(fp, fullname);
961 #endif
963 /* get the text buffer contents and its length */
964 fileString = BufGetAll(window->buffer);
965 fileLen = window->buffer->length;
967 /* If null characters are substituted for, put them back */
968 BufUnsubstituteNullChars(fileString, window->buffer);
970 /* If the file is to be saved in DOS or Macintosh format, reconvert */
971 if (window->fileFormat == DOS_FILE_FORMAT)
973 if (!ConvertToDosFileString(&fileString, &fileLen))
975 DialogF(DF_ERR, window->shell, 1, "Out of Memory",
976 "Out of memory! Try\nsaving in Unix format", "OK");
977 return FALSE;
979 } else if (window->fileFormat == MAC_FILE_FORMAT)
981 ConvertToMacFileString(fileString, fileLen);
984 /* write to the file */
985 #ifdef IBM_FWRITE_BUG
986 write(fileno(fp), fileString, fileLen);
987 #else
988 fwrite(fileString, sizeof(char), fileLen, fp);
989 #endif
990 if (ferror(fp))
992 DialogF(DF_ERR, window->shell, 1, "Error saving File",
993 "%s not saved:\n%s", "OK", window->filename, errorString());
994 fclose(fp);
995 remove(fullname);
996 XtFree(fileString);
997 return FALSE;
1000 /* close the file */
1001 if (fclose(fp) != 0)
1003 DialogF(DF_ERR, window->shell, 1, "Error closing File",
1004 "Error closing file:\n%s", "OK", errorString());
1005 XtFree(fileString);
1006 return FALSE;
1009 /* free the text buffer copy returned from XmTextGetString */
1010 XtFree(fileString);
1012 #ifdef VMS
1013 /* reflect the fact that NEdit is now editing a new version of the file */
1014 ParseFilename(fullname, window->filename, window->path);
1015 #endif /*VMS*/
1017 /* success, file was written */
1018 SetWindowModified(window, FALSE);
1020 /* update the modification time */
1021 if (stat(fullname, &statbuf) == 0) {
1022 window->lastModTime = statbuf.st_mtime;
1023 window->fileMissing = FALSE;
1024 } else {
1025 /* This needs to produce an error message -- the file can't be
1026 accessed! */
1027 window->lastModTime = 0;
1028 window->fileMissing = TRUE;
1031 return TRUE;
1035 ** Create a backup file for the current window. The name for the backup file
1036 ** is generated using the name and path stored in the window and adding a
1037 ** tilde (~) on UNIX and underscore (_) on VMS to the beginning of the name.
1039 int WriteBackupFile(WindowInfo *window)
1041 char *fileString = NULL;
1042 char name[MAXPATHLEN];
1043 FILE *fp;
1044 int fd, fileLen;
1046 /* Generate a name for the autoSave file */
1047 backupFileName(window, name, sizeof(name));
1049 /* remove the old backup file.
1050 Well, this might fail - we'll notice later however. */
1051 remove(name);
1053 /* open the file, set more restrictive permissions (using default
1054 permissions was somewhat of a security hole, because permissions were
1055 independent of those of the original file being edited */
1056 #ifdef VMS
1057 if ((fp = fopen(name, "w", "rfm = stmlf")) == NULL)
1058 #else
1059 if ((fd = open(name, O_CREAT|O_EXCL|O_WRONLY, S_IRUSR | S_IWUSR)) < 0
1060 || (fp = fdopen(fd, "w")) == NULL)
1061 #endif /* VMS */
1063 DialogF(DF_WARN, window->shell, 1, "Error writing Backup",
1064 "Unable to save backup for %s:\n%s\n"
1065 "Automatic backup is now off", "OK", window->filename,
1066 errorString());
1067 window->autoSave = FALSE;
1068 SetToggleButtonState(window, window->autoSaveItem, FALSE, FALSE);
1069 return FALSE;
1072 /* Set VMS permissions */
1073 #ifdef VMS
1074 chmod(name, S_IRUSR | S_IWUSR);
1075 #endif
1077 /* get the text buffer contents and its length */
1078 fileString = BufGetAll(window->buffer);
1079 fileLen = window->buffer->length;
1081 /* If null characters are substituted for, put them back */
1082 BufUnsubstituteNullChars(fileString, window->buffer);
1084 /* add a terminating newline if the file doesn't already have one */
1085 if (fileLen != 0 && fileString[fileLen-1] != '\n')
1086 fileString[fileLen++] = '\n'; /* null terminator no longer needed */
1088 /* write out the file */
1089 #ifdef IBM_FWRITE_BUG
1090 write(fileno(fp), fileString, fileLen);
1091 #else
1092 fwrite(fileString, sizeof(char), fileLen, fp);
1093 #endif
1094 if (ferror(fp))
1096 DialogF(DF_ERR, window->shell, 1, "Error saving Backup",
1097 "Error while saving backup for %s:\n%s\n"
1098 "Automatic backup is now off", "OK", window->filename,
1099 errorString());
1100 fclose(fp);
1101 remove(name);
1102 XtFree(fileString);
1103 window->autoSave = FALSE;
1104 return FALSE;
1107 /* close the backup file */
1108 if (fclose(fp) != 0) {
1109 XtFree(fileString);
1110 return FALSE;
1113 /* Free the text buffer copy returned from XmTextGetString */
1114 XtFree(fileString);
1116 return TRUE;
1120 ** Remove the backup file associated with this window
1122 void RemoveBackupFile(WindowInfo *window)
1124 char name[MAXPATHLEN];
1126 /* Don't delete backup files when backups aren't activated. */
1127 if (window->autoSave == FALSE)
1128 return;
1130 backupFileName(window, name, sizeof(name));
1131 remove(name);
1135 ** Generate the name of the backup file for this window from the filename
1136 ** and path in the window data structure & write into name
1138 static void backupFileName(WindowInfo *window, char *name, int len)
1140 char bckname[MAXPATHLEN];
1141 #ifdef VMS
1142 if (window->filenameSet)
1143 sprintf(name, "%s_%s", window->path, window->filename);
1144 else
1145 sprintf(name, "%s_%s", "SYS$LOGIN:", window->filename);
1146 #else
1147 if (window->filenameSet)
1149 sprintf(name, "%s~%s", window->path, window->filename);
1150 } else
1152 strcpy(bckname, "~");
1153 strncat(bckname, window->filename, MAXPATHLEN - 1);
1154 PrependHome(bckname, name, len);
1156 #endif /*VMS*/
1160 ** If saveOldVersion is on, copies the existing version of the file to
1161 ** <filename>.bck in anticipation of a new version being saved. Returns
1162 ** True if backup fails and user requests that the new file not be written.
1164 static int writeBckVersion(WindowInfo *window)
1166 #ifndef VMS
1167 char fullname[MAXPATHLEN], bckname[MAXPATHLEN];
1168 struct stat statbuf;
1169 FILE *inFP, *outFP;
1170 int fd, fileLen;
1171 char *fileString;
1173 /* Do only if version backups are turned on */
1174 if (!window->saveOldVersion) {
1175 return False;
1178 /* Get the full name of the file */
1179 strcpy(fullname, window->path);
1180 strcat(fullname, window->filename);
1182 /* Generate name for old version */
1183 if ((int)(strlen(fullname) + 5) > (int)MAXPATHLEN)
1185 return bckError(window, "file name too long", window->filename);
1187 sprintf(bckname, "%s.bck", fullname);
1189 /* Delete the old backup file */
1190 /* Errors are ignored; we'll notice them later. */
1191 unlink(bckname);
1193 /* open the file being edited. If there are problems with the
1194 old file, don't bother the user, just skip the backup */
1195 inFP = fopen(fullname, "rb");
1196 if (inFP == NULL) {
1197 return FALSE;
1200 /* find the length of the file */
1201 if (fstat(fileno(inFP), &statbuf) != 0) {
1202 return FALSE;
1204 fileLen = statbuf.st_size;
1206 /* open the file exclusive and with restrictive permissions. */
1207 #ifdef VMS
1208 if ((outFP = fopen(bckname, "w", "rfm = stmlf")) == NULL) {
1209 #else
1210 if ((fd = open(bckname, O_CREAT|O_EXCL|O_WRONLY, S_IRUSR | S_IWUSR)) < 0
1211 || (outFP = fdopen(fd, "wb")) == NULL) {
1212 #endif /* VMS */
1213 fclose(inFP);
1214 return bckError(window, "Error open backup file", bckname);
1216 #ifdef VMS
1217 chmod(bckname, S_IRUSR | S_IWUSR);
1218 #endif
1220 /* Allocate space for the whole contents of the file */
1221 fileString = (char *)malloc(fileLen);
1222 if (fileString == NULL) {
1223 fclose(inFP);
1224 fclose(outFP);
1225 return bckError(window, "out of memory", bckname);
1228 /* read the file into fileString */
1229 fread(fileString, sizeof(char), fileLen, inFP);
1230 if (ferror(inFP)) {
1231 fclose(inFP);
1232 fclose(outFP);
1233 free(fileString);
1234 return FALSE;
1237 /* close the input file, ignore any errors */
1238 fclose(inFP);
1240 /* write to the file */
1241 #ifdef IBM_FWRITE_BUG
1242 write(fileno(outFP), fileString, fileLen);
1243 #else
1244 fwrite(fileString, sizeof(char), fileLen, outFP);
1245 #endif
1246 if (ferror(outFP)) {
1247 fclose(outFP);
1248 remove(bckname);
1249 free(fileString);
1250 return bckError(window, errorString(), bckname);
1252 free(fileString);
1254 /* close the file */
1255 if (fclose(outFP) != 0)
1256 return bckError(window, errorString(), bckname);
1257 #endif /* VMS */
1259 return FALSE;
1263 ** Error processing for writeBckVersion, gives the user option to cancel
1264 ** the subsequent save, or continue and optionally turn off versioning
1266 static int bckError(WindowInfo *window, const char *errString, const char *file)
1268 int resp;
1270 resp = DialogF(DF_ERR, window->shell, 3, "Error writing Backup",
1271 "Couldn't write .bck (last version) file.\n%s: %s", "Cancel Save",
1272 "Turn off Backups", "Continue", file, errString);
1273 if (resp == 1)
1274 return TRUE;
1275 if (resp == 2) {
1276 window->saveOldVersion = FALSE;
1277 SetToggleButtonState(window, window->saveLastItem, FALSE, FALSE);
1279 return FALSE;
1282 void PrintWindow(WindowInfo *window, int selectedOnly)
1284 textBuffer *buf = window->buffer;
1285 selection *sel = &buf->primary;
1286 char *fileString = NULL;
1287 int fileLen;
1289 /* get the contents of the text buffer from the text area widget. Add
1290 wrapping newlines if necessary to make it match the displayed text */
1291 if (selectedOnly) {
1292 if (!sel->selected) {
1293 XBell(TheDisplay, 0);
1294 return;
1296 if (sel->rectangular) {
1297 fileString = BufGetSelectionText(buf);
1298 fileLen = strlen(fileString);
1299 } else
1300 fileString = TextGetWrapped(window->textArea, sel->start, sel->end,
1301 &fileLen);
1302 } else
1303 fileString = TextGetWrapped(window->textArea, 0, buf->length, &fileLen);
1305 /* If null characters are substituted for, put them back */
1306 BufUnsubstituteNullChars(fileString, buf);
1308 /* add a terminating newline if the file doesn't already have one */
1309 if (fileLen != 0 && fileString[fileLen-1] != '\n')
1310 fileString[fileLen++] = '\n'; /* null terminator no longer needed */
1312 /* Print the string */
1313 PrintString(fileString, fileLen, window->shell, window->filename);
1315 /* Free the text buffer copy returned from XmTextGetString */
1316 XtFree(fileString);
1320 ** Print a string (length is required). parent is the dialog parent, for
1321 ** error dialogs, and jobName is the print title.
1323 void PrintString(const char *string, int length, Widget parent, const char *jobName)
1325 char tmpFileName[L_tmpnam]; /* L_tmpnam defined in stdio.h */
1326 FILE *fp;
1327 int fd;
1329 /* Generate a temporary file name */
1330 /* If the glibc is used, the linker issues a warning at this point. This is
1331 very thoughtful of him, but does not apply to NEdit. The recommended
1332 replacement mkstemp(3) uses the same algorithm as NEdit, namely
1333 1. Create a filename
1334 2. Open the file with the O_CREAT|O_EXCL flags
1335 So all an attacker can do is a DoS on the print function. */
1336 tmpnam(tmpFileName);
1338 /* open the temporary file */
1339 #ifdef VMS
1340 if ((fp = fopen(tmpFileName, "w", "rfm = stmlf")) == NULL)
1341 #else
1342 if ((fd = open(tmpFileName, O_CREAT|O_EXCL|O_WRONLY, S_IRUSR | S_IWUSR)) < 0 || (fp = fdopen(fd, "w")) == NULL)
1343 #endif /* VMS */
1345 DialogF(DF_WARN, parent, 1, "Error while Printing",
1346 "Unable to write file for printing:\n%s", "OK",
1347 errorString());
1348 return;
1351 #ifdef VMS
1352 chmod(tmpFileName, S_IRUSR | S_IWUSR);
1353 #endif
1355 /* write to the file */
1356 #ifdef IBM_FWRITE_BUG
1357 write(fileno(fp), string, length);
1358 #else
1359 fwrite(string, sizeof(char), length, fp);
1360 #endif
1361 if (ferror(fp))
1363 DialogF(DF_ERR, parent, 1, "Error while Printing",
1364 "%s not printed:\n%s", "OK", jobName, errorString());
1365 fclose(fp); /* should call close(fd) in turn! */
1366 remove(tmpFileName);
1367 return;
1370 /* close the temporary file */
1371 if (fclose(fp) != 0)
1373 DialogF(DF_ERR, parent, 1, "Error while Printing",
1374 "Error closing temp. print file:\n%s", "OK",
1375 errorString());
1376 remove(tmpFileName);
1377 return;
1380 /* Print the temporary file, then delete it and return success */
1381 #ifdef VMS
1382 strcat(tmpFileName, ".");
1383 PrintFile(parent, tmpFileName, jobName, True);
1384 #else
1385 PrintFile(parent, tmpFileName, jobName);
1386 remove(tmpFileName);
1387 #endif /*VMS*/
1388 return;
1392 ** Wrapper for GetExistingFilename which uses the current window's path
1393 ** (if set) as the default directory.
1395 int PromptForExistingFile(WindowInfo *window, char *prompt, char *fullname)
1397 char *savedDefaultDir;
1398 int retVal;
1400 /* Temporarily set default directory to window->path, prompt for file,
1401 then, if the call was unsuccessful, restore the original default
1402 directory */
1403 savedDefaultDir = GetFileDialogDefaultDirectory();
1404 if (*window->path != '\0')
1405 SetFileDialogDefaultDirectory(window->path);
1406 retVal = GetExistingFilename(window->shell, prompt, fullname);
1407 if (retVal != GFN_OK)
1408 SetFileDialogDefaultDirectory(savedDefaultDir);
1409 if (savedDefaultDir != NULL)
1410 XtFree(savedDefaultDir);
1411 return retVal;
1415 ** Wrapper for HandleCustomNewFileSB which uses the current window's path
1416 ** (if set) as the default directory, and asks about embedding newlines
1417 ** to make wrapping permanent.
1419 int PromptForNewFile(WindowInfo *window, char *prompt, char *fullname,
1420 int *fileFormat, int *addWrap)
1422 int n, retVal;
1423 Arg args[20];
1424 XmString s1, s2;
1425 Widget fileSB, wrapToggle;
1426 Widget formatForm, formatBtns, unixFormat, dosFormat, macFormat;
1427 char *savedDefaultDir;
1429 *fileFormat = window->fileFormat;
1431 /* Temporarily set default directory to window->path, prompt for file,
1432 then, if the call was unsuccessful, restore the original default
1433 directory */
1434 savedDefaultDir = GetFileDialogDefaultDirectory();
1435 if (*window->path != '\0')
1436 SetFileDialogDefaultDirectory(window->path);
1438 /* Present a file selection dialog with an added field for requesting
1439 long line wrapping to become permanent via inserted newlines */
1440 n = 0;
1441 XtSetArg(args[n],
1442 XmNselectionLabelString,
1443 s1 = XmStringCreateLocalized("New File Name:")); n++;
1444 XtSetArg(args[n], XmNdialogStyle, XmDIALOG_FULL_APPLICATION_MODAL); n++;
1445 XtSetArg(args[n],
1446 XmNdialogTitle,
1447 s2 = XmStringCreateSimple(prompt)); n++;
1448 fileSB = CreateFileSelectionDialog(window->shell,"FileSelect",args,n);
1449 XmStringFree(s1);
1450 XmStringFree(s2);
1451 formatForm = XtVaCreateManagedWidget("formatForm", xmFormWidgetClass,
1452 fileSB, NULL);
1453 formatBtns = XtVaCreateManagedWidget("formatBtns", xmRowColumnWidgetClass,
1454 formatForm,
1455 XmNradioBehavior, XmONE_OF_MANY,
1456 XmNorientation, XmHORIZONTAL,
1457 XmNpacking, XmPACK_TIGHT,
1458 XmNtopAttachment, XmATTACH_FORM,
1459 XmNleftAttachment, XmATTACH_FORM, NULL);
1460 XtVaCreateManagedWidget("formatBtns", xmLabelWidgetClass, formatBtns,
1461 XmNlabelString, s1=XmStringCreateSimple("Format:"), NULL);
1462 XmStringFree(s1);
1463 unixFormat = XtVaCreateManagedWidget("unixFormat",
1464 xmToggleButtonWidgetClass, formatBtns, XmNlabelString,
1465 s1=XmStringCreateSimple("Unix"),
1466 XmNset, *fileFormat == UNIX_FILE_FORMAT,
1467 XmNuserData, UNIX_FILE_FORMAT,
1468 XmNmarginHeight, 0, XmNalignment, XmALIGNMENT_BEGINNING,
1469 XmNmnemonic, 'U', NULL);
1470 XmStringFree(s1);
1471 XtAddCallback(unixFormat, XmNvalueChangedCallback, setFormatCB,
1472 fileFormat);
1473 dosFormat = XtVaCreateManagedWidget("dosFormat",
1474 xmToggleButtonWidgetClass, formatBtns, XmNlabelString,
1475 s1=XmStringCreateSimple("DOS"),
1476 XmNset, *fileFormat == DOS_FILE_FORMAT,
1477 XmNuserData, DOS_FILE_FORMAT,
1478 XmNmarginHeight, 0, XmNalignment, XmALIGNMENT_BEGINNING,
1479 XmNmnemonic, 'O', NULL);
1480 XmStringFree(s1);
1481 XtAddCallback(dosFormat, XmNvalueChangedCallback, setFormatCB,
1482 fileFormat);
1483 macFormat = XtVaCreateManagedWidget("macFormat",
1484 xmToggleButtonWidgetClass, formatBtns, XmNlabelString,
1485 s1=XmStringCreateSimple("Macintosh"),
1486 XmNset, *fileFormat == MAC_FILE_FORMAT,
1487 XmNuserData, MAC_FILE_FORMAT,
1488 XmNmarginHeight, 0, XmNalignment, XmALIGNMENT_BEGINNING,
1489 XmNmnemonic, 'M', NULL);
1490 XmStringFree(s1);
1491 XtAddCallback(macFormat, XmNvalueChangedCallback, setFormatCB,
1492 fileFormat);
1493 if (window->wrapMode == CONTINUOUS_WRAP) {
1494 wrapToggle = XtVaCreateManagedWidget("addWrap",
1495 xmToggleButtonWidgetClass, formatForm, XmNlabelString,
1496 s1=XmStringCreateSimple("Add line breaks where wrapped"),
1497 XmNalignment, XmALIGNMENT_BEGINNING,
1498 XmNmnemonic, 'A',
1499 XmNtopAttachment, XmATTACH_WIDGET,
1500 XmNtopWidget, formatBtns,
1501 XmNleftAttachment, XmATTACH_FORM, NULL);
1502 XtAddCallback(wrapToggle, XmNvalueChangedCallback, addWrapCB,
1503 addWrap);
1504 XmStringFree(s1);
1506 *addWrap = False;
1507 XtVaSetValues(XmFileSelectionBoxGetChild(fileSB,
1508 XmDIALOG_FILTER_LABEL), XmNmnemonic, 'l', XmNuserData,
1509 XmFileSelectionBoxGetChild(fileSB, XmDIALOG_FILTER_TEXT), NULL);
1510 XtVaSetValues(XmFileSelectionBoxGetChild(fileSB,
1511 XmDIALOG_DIR_LIST_LABEL), XmNmnemonic, 'D', XmNuserData,
1512 XmFileSelectionBoxGetChild(fileSB, XmDIALOG_DIR_LIST), NULL);
1513 XtVaSetValues(XmFileSelectionBoxGetChild(fileSB,
1514 XmDIALOG_LIST_LABEL), XmNmnemonic, 'F', XmNuserData,
1515 XmFileSelectionBoxGetChild(fileSB, XmDIALOG_LIST), NULL);
1516 XtVaSetValues(XmFileSelectionBoxGetChild(fileSB,
1517 XmDIALOG_SELECTION_LABEL), XmNmnemonic,
1518 prompt[strspn(prompt, "lFD")], XmNuserData,
1519 XmFileSelectionBoxGetChild(fileSB, XmDIALOG_TEXT), NULL);
1520 AddDialogMnemonicHandler(fileSB, FALSE);
1521 RemapDeleteKey(XmFileSelectionBoxGetChild(fileSB, XmDIALOG_FILTER_TEXT));
1522 RemapDeleteKey(XmFileSelectionBoxGetChild(fileSB, XmDIALOG_TEXT));
1523 retVal = HandleCustomNewFileSB(fileSB, fullname,
1524 window->filenameSet ? window->filename : NULL);
1526 if (retVal != GFN_OK)
1527 SetFileDialogDefaultDirectory(savedDefaultDir);
1528 if (savedDefaultDir != NULL)
1529 XtFree(savedDefaultDir);
1530 return retVal;
1534 ** Find a name for an untitled file, unique in the name space of in the opened
1535 ** files in this session, i.e. Untitled or Untitled_nn, and write it into
1536 ** the string "name".
1538 void UniqueUntitledName(char *name)
1540 WindowInfo *w;
1541 int i;
1543 for (i=0; i<INT_MAX; i++) {
1544 if (i == 0)
1545 sprintf(name, "Untitled");
1546 else
1547 sprintf(name, "Untitled_%d", i);
1548 for (w=WindowList; w!=NULL; w=w->next)
1549 if (!strcmp(w->filename, name))
1550 break;
1551 if (w == NULL)
1552 break;
1557 ** Check if the file in the window was changed by an external source.
1558 ** and put up a warning dialog if it has.
1560 void CheckForChangesToFile(WindowInfo *window)
1562 static WindowInfo *lastCheckWindow;
1563 static Time lastCheckTime = 0;
1564 char fullname[MAXPATHLEN];
1565 struct stat statbuf;
1566 Time timestamp;
1567 FILE *fp;
1568 int resp, silent = 0;
1569 XWindowAttributes winAttr;
1571 if(!window->filenameSet)
1572 return;
1574 /* If last check was very recent, don't impact performance */
1575 timestamp = XtLastTimestampProcessed(XtDisplay(window->shell));
1576 if (window == lastCheckWindow &&
1577 timestamp - lastCheckTime < MOD_CHECK_INTERVAL)
1578 return;
1579 lastCheckWindow = window;
1580 lastCheckTime = timestamp;
1582 /* Update the status, but don't pop up a dialog if we're called
1583 from a place where the window might be iconic (e.g., from the
1584 replace dialog) or on another desktop.
1586 This works, but I bet it costs a round-trip to the server.
1587 Might be better to capture MapNotify/Unmap events instead.
1589 For tabs that are not on top, we don't want the dialog either,
1590 and we don't even need to contact the server to find out. By
1591 performing this check first, we avoid a server round-trip for
1592 most files in practice. */
1593 if (!IsTopDocument(window))
1594 silent = 1;
1595 else {
1596 XGetWindowAttributes(XtDisplay(window->shell),
1597 XtWindow(window->shell),
1598 &winAttr);
1600 if (winAttr.map_state != IsViewable)
1601 silent = 1;
1604 /* Get the file mode and modification time */
1605 strcpy(fullname, window->path);
1606 strcat(fullname, window->filename);
1607 if (stat(fullname, &statbuf) != 0) {
1608 /* Return if we've already warned the user or we can't warn him now */
1609 if (window->fileMissing || silent)
1610 return;
1611 /* Can't stat the file -- maybe it's been deleted.
1612 The filename is now invalid */
1613 window->fileMissing = TRUE;
1614 window->lastModTime = 1;
1615 /* Warn the user, if they like to be warned (Maybe this should be its
1616 own preference setting: GetPrefWarnFileDeleted() ) */
1617 if (GetPrefWarnFileMods()) {
1618 /* See note below about pop-up timing and XUngrabPointer */
1619 XUngrabPointer(XtDisplay(window->shell), timestamp);
1620 if( errno == EACCES )
1621 resp = 1 + DialogF(DF_ERR, window->shell, 2,
1622 "File not Accessible",
1623 "You no longer have access to file \"%s\".\n"
1624 "Another program may have changed the permissions one of\n"
1625 "its parent directories.\nSave as a new file?",
1626 "Save As...", "Cancel", window->filename);
1627 else
1628 resp = DialogF(DF_ERR, window->shell, 3, "File not found",
1629 "Error while checking the status of file \"%s\":\n"
1630 " \"%s\"\n"
1631 "Another program may have deleted or moved it.\n"
1632 "Re-Save file or Save as a new file?", "Re-Save",
1633 "Save As...", "Cancel", window->filename,
1634 errorString());
1635 if (resp == 1)
1636 SaveWindow(window);
1637 else if (resp == 2)
1638 SaveWindowAs(window, NULL, 0);
1640 /* A missing or (re-)saved file can't be read-only. */
1641 SET_PERM_LOCKED(window->lockReasons, False);
1642 UpdateWindowTitle(window);
1643 UpdateWindowReadOnly(window);
1644 return;
1647 /* Check that the file's read-only status is still correct (but
1648 only if the file can still be opened successfully in read mode) */
1649 if (window->fileMode != statbuf.st_mode) {
1650 window->fileMode = statbuf.st_mode;
1651 if ((fp = fopen(fullname, "r")) != NULL) {
1652 int readOnly;
1653 fclose(fp);
1654 #ifndef DONT_USE_ACCESS
1655 readOnly = access(fullname, W_OK) != 0;
1656 #else
1657 if (((fp = fopen(fullname, "r+")) != NULL)) {
1658 readOnly = FALSE;
1659 fclose(fp);
1660 } else
1661 readOnly = TRUE;
1662 #endif
1663 if (IS_PERM_LOCKED(window->lockReasons) != readOnly) {
1664 SET_PERM_LOCKED(window->lockReasons, readOnly);
1665 UpdateWindowTitle(window);
1666 UpdateWindowReadOnly(window);
1671 /* Warn the user if the file has been modified, unless checking is
1672 turned off or the user has already been warned. Popping up a dialog
1673 from a focus callback (which is how this routine is usually called)
1674 seems to catch Motif off guard, and if the timing is just right, the
1675 dialog can be left with a still active pointer grab from a Motif menu
1676 which is still in the process of popping down. The workaround, below,
1677 of calling XUngrabPointer is inelegant but seems to fix the problem. */
1678 if (!silent &&
1679 ((window->lastModTime != 0 &&
1680 window->lastModTime != statbuf.st_mtime) ||
1681 window->fileMissing) ){
1682 window->lastModTime = 0; /* Inhibit further warnings */
1683 window->fileMissing = FALSE;
1684 if (!GetPrefWarnFileMods())
1685 return;
1686 if (GetPrefWarnRealFileMods() &&
1687 !cmpWinAgainstFile(window, fullname)) {
1688 /* Contents hasn't changed. Update the modification time. */
1689 window->lastModTime = statbuf.st_mtime;
1690 return;
1692 XUngrabPointer(XtDisplay(window->shell), timestamp);
1693 if (window->fileChanged)
1694 resp = DialogF(DF_WARN, window->shell, 2,
1695 "File modified externally",
1696 "%s has been modified by another program. Reload?\n\n"
1697 "WARNING: Reloading will discard changes made in this\n"
1698 "editing session!", "Reload", "Cancel", window->filename);
1699 else
1700 resp = DialogF(DF_WARN, window->shell, 2,
1701 "File modified externally",
1702 "%s has been modified by another\nprogram. Reload?",
1703 "Reload", "Cancel", window->filename);
1704 if (resp == 1)
1705 RevertToSaved(window);
1710 ** Return true if the file displayed in window has been modified externally
1711 ** to nedit. This should return FALSE if the file has been deleted or is
1712 ** unavailable.
1714 static int fileWasModifiedExternally(WindowInfo *window)
1716 char fullname[MAXPATHLEN];
1717 struct stat statbuf;
1719 if(!window->filenameSet)
1720 return FALSE;
1721 /* if (window->lastModTime == 0)
1722 return FALSE; */
1723 strcpy(fullname, window->path);
1724 strcat(fullname, window->filename);
1725 if (stat(fullname, &statbuf) != 0)
1726 return FALSE;
1727 if (window->lastModTime == statbuf.st_mtime)
1728 return FALSE;
1729 if (GetPrefWarnRealFileMods() &&
1730 !cmpWinAgainstFile(window, fullname)) {
1731 return FALSE;
1733 return TRUE;
1737 ** Check the read-only or locked status of the window and beep and return
1738 ** false if the window should not be written in.
1740 int CheckReadOnly(WindowInfo *window)
1742 if (IS_ANY_LOCKED(window->lockReasons)) {
1743 XBell(TheDisplay, 0);
1744 return True;
1746 return False;
1750 ** Wrapper for strerror so all the calls don't have to be ifdef'd for VMS.
1752 static const char *errorString(void)
1754 #ifdef VMS
1755 return strerror(errno, vaxc$errno);
1756 #else
1757 return strerror(errno);
1758 #endif
1761 #ifdef VMS
1763 ** Removing the VMS version number from a file name (if has one).
1765 void removeVersionNumber(char *fileName)
1767 char *versionStart;
1769 versionStart = strrchr(fileName, ';');
1770 if (versionStart != NULL)
1771 *versionStart = '\0';
1773 #endif /*VMS*/
1776 ** Callback procedure for File Format toggle buttons. Format is stored
1777 ** in userData field of widget button
1779 static void setFormatCB(Widget w, XtPointer clientData, XtPointer callData)
1781 if (XmToggleButtonGetState(w))
1782 XtVaGetValues(w, XmNuserData, clientData, NULL);
1786 ** Callback procedure for toggle button requesting newlines to be inserted
1787 ** to emulate continuous wrapping.
1789 static void addWrapCB(Widget w, XtPointer clientData, XtPointer callData)
1791 int resp;
1792 int *addWrap = (int *)clientData;
1794 if (XmToggleButtonGetState(w))
1796 resp = DialogF(DF_WARN, w, 2, "Add Wrap",
1797 "This operation adds permanent line breaks to\n"
1798 "match the automatic wrapping done by the\n"
1799 "Continuous Wrap mode Preferences Option.\n\n"
1800 "*** This Option is Irreversable ***\n\n"
1801 "Once newlines are inserted, continuous wrapping\n"
1802 "will no longer work automatically on these lines", "OK",
1803 "Cancel");
1804 if (resp == 2)
1806 XmToggleButtonSetState(w, False, False);
1807 *addWrap = False;
1808 } else
1810 *addWrap = True;
1812 } else
1814 *addWrap = False;
1819 ** Change a window created in NEdit's continuous wrap mode to the more
1820 ** conventional Unix format of embedded newlines. Indicate to the user
1821 ** by turning off Continuous Wrap mode.
1823 static void addWrapNewlines(WindowInfo *window)
1825 int fileLen, i, insertPositions[MAX_PANES], topLines[MAX_PANES];
1826 int horizOffset;
1827 Widget text;
1828 char *fileString;
1830 /* save the insert and scroll positions of each pane */
1831 for (i=0; i<=window->nPanes; i++) {
1832 text = i==0 ? window->textArea : window->textPanes[i-1];
1833 insertPositions[i] = TextGetCursorPos(text);
1834 TextGetScroll(text, &topLines[i], &horizOffset);
1837 /* Modify the buffer to add wrapping */
1838 fileString = TextGetWrapped(window->textArea, 0,
1839 window->buffer->length, &fileLen);
1840 BufSetAll(window->buffer, fileString);
1841 XtFree(fileString);
1843 /* restore the insert and scroll positions of each pane */
1844 for (i=0; i<=window->nPanes; i++) {
1845 text = i==0 ? window->textArea : window->textPanes[i-1];
1846 TextSetCursorPos(text, insertPositions[i]);
1847 TextSetScroll(text, topLines[i], 0);
1850 /* Show the user that something has happened by turning off
1851 Continuous Wrap mode */
1852 SetToggleButtonState(window, window->continuousWrapItem, False, True);
1856 * Number of bytes read at once by cmpWinAgainstFile
1858 #define PREFERRED_CMPBUF_LEN 32768
1861 * Check if the contens of the textBuffer *buf is equal
1862 * the contens of the file named fileName. The format of
1863 * the file (UNIX/DOS/MAC) is handled properly.
1865 * Return values
1866 * 0: no difference found
1867 * !0: difference found or could not compare contents.
1869 static int cmpWinAgainstFile(WindowInfo *window, const char *fileName)
1871 char fileString[PREFERRED_CMPBUF_LEN + 2];
1872 struct stat statbuf;
1873 int fileLen, restLen, nRead, bufPos, rv, offset, filePos;
1874 char pendingCR = 0;
1875 int fileFormat = window->fileFormat;
1876 char message[MAXPATHLEN+50];
1877 textBuffer *buf = window->buffer;
1878 FILE *fp;
1880 fp = fopen(fileName, "r");
1881 if (!fp)
1882 return (1);
1883 if (fstat(fileno(fp), &statbuf) != 0) {
1884 fclose(fp);
1885 return (1);
1888 fileLen = statbuf.st_size;
1889 /* For DOS files, we can't simply check the length */
1890 if (fileFormat != DOS_FILE_FORMAT) {
1891 if (fileLen != buf->length) {
1892 fclose(fp);
1893 return (1);
1895 } else {
1896 /* If a DOS file is smaller on disk, it's certainly different */
1897 if (fileLen < buf->length) {
1898 fclose(fp);
1899 return (1);
1903 /* For large files, the comparison can take a while. If it takes too long,
1904 the user should be given a clue about what is happening. */
1905 sprintf(message, "Comparing externally modified %s ...", window->filename);
1906 restLen = min(PREFERRED_CMPBUF_LEN, fileLen);
1907 bufPos = 0;
1908 filePos = 0;
1909 while (restLen > 0) {
1910 AllWindowsBusy(message);
1911 if (pendingCR) {
1912 fileString[0] = pendingCR;
1913 offset = 1;
1914 } else {
1915 offset = 0;
1918 nRead = fread(fileString+offset, sizeof(char), restLen, fp);
1919 if (nRead != restLen) {
1920 fclose(fp);
1921 AllWindowsUnbusy();
1922 return (1);
1924 filePos += nRead;
1926 nRead += offset;
1928 if (fileFormat == MAC_FILE_FORMAT)
1929 ConvertFromMacFileString(fileString, nRead);
1930 else if (fileFormat == DOS_FILE_FORMAT)
1931 ConvertFromDosFileString(fileString, &nRead, &pendingCR);
1933 /* Beware of 0 chars ! */
1934 BufSubstituteNullChars(fileString, nRead, buf);
1935 rv = BufCmp(buf, bufPos, nRead, fileString);
1936 if (rv) {
1937 fclose(fp);
1938 AllWindowsUnbusy();
1939 return (rv);
1941 bufPos += nRead;
1942 restLen = min(fileLen - filePos, PREFERRED_CMPBUF_LEN);
1944 AllWindowsUnbusy();
1945 fclose(fp);
1946 if (pendingCR) {
1947 rv = BufCmp(buf, bufPos, 1, &pendingCR);
1948 if (rv) {
1949 return (rv);
1951 bufPos += 1;
1953 if (bufPos != buf->length) {
1954 return (1);
1956 return (0);
1959 static int min(int i1, int i2)
1961 return i1 <= i2 ? i1 : i2;