Minor fix.
[nedit.git] / source / file.c
blob1ff5af2362554a429a810a9796115a7002e78ee2
1 static const char CVSID[] = "$Id: file.c,v 1.94 2005/05/27 16:49:04 edg 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 #include <unistd.h>
62 #else
63 #include <sys/types.h>
64 #include <sys/stat.h>
65 #ifndef __MVS__
66 #include <sys/param.h>
67 #endif
68 #include <unistd.h>
69 #include <fcntl.h>
70 #endif /*VMS*/
72 #include <Xm/Xm.h>
73 #include <Xm/ToggleB.h>
74 #include <Xm/FileSB.h>
75 #include <Xm/RowColumn.h>
76 #include <Xm/Form.h>
77 #include <Xm/Label.h>
79 #ifdef HAVE_DEBUG_H
80 #include "../debug.h"
81 #endif
83 /* Maximum frequency in miliseconds of checking for external modifications.
84 The periodic check is only performed on buffer modification, and the check
85 interval is only to prevent checking on every keystroke in case of a file
86 system which is slow to process stat requests (which I'm not sure exists) */
87 #define MOD_CHECK_INTERVAL 3000
89 static int doSave(WindowInfo *window);
90 static void safeClose(WindowInfo *window);
91 static int doOpen(WindowInfo *window, const char *name, const char *path,
92 int flags);
93 static void backupFileName(WindowInfo *window, char *name, 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);
104 #ifdef VMS
105 void removeVersionNumber(char *fileName);
106 #endif /*VMS*/
108 WindowInfo *EditNewFile(WindowInfo *inWindow, char *geometry, int iconic,
109 const char *languageMode, const char *defaultPath)
111 char name[MAXPATHLEN];
112 WindowInfo *window;
114 /*... test for creatability? */
116 /* Find a (relatively) unique name for the new file */
117 UniqueUntitledName(name);
119 /* create new window/document */
120 if (inWindow)
121 window = CreateDocument(inWindow, name, geometry, iconic);
122 else
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);
136 else
137 SetLanguageMode(window, FindLanguageMode(languageMode), True);
139 ShowTabBar(window, GetShowTabBar(window));
141 if (iconic && IsIconic(window))
142 RaiseDocument(window);
143 else
144 RaiseDocumentWindow(window);
146 SortTabBar(window);
147 return 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
154 ** can be any of:
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)
173 WindowInfo *window;
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) {
179 if (!bgOpen) {
180 if (iconic)
181 RaiseDocument(window);
182 else
183 RaiseDocumentWindow(window);
185 return 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) {
196 if (tabbed) {
197 window = CreateDocument(inWindow, name, geometry, iconic);
199 else {
200 window = CreateWindow(name, geometry, iconic);
203 else {
204 /* open file in untitled document */
205 window = inWindow;
206 strcpy(window->path, path);
207 strcpy(window->filename, name);
208 if (!iconic && !bgOpen) {
209 RaiseDocumentWindow(window);
213 /* Open the file */
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 */
217 safeClose(window);
219 return NULL;
222 /* Decide what language mode to use, trigger language specific actions */
223 if (languageMode == NULL)
224 DetermineLanguageMode(window, True);
225 else
226 SetLanguageMode(window, FindLanguageMode(languageMode), True);
228 /* update tab label and tooltip */
229 RefreshTabState(window);
230 SortTabBar(window);
231 ShowTabBar(window, GetShowTabBar(window));
233 if (!bgOpen)
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);
249 return window;
252 void RevertToSaved(WindowInfo *window)
254 char name[MAXPATHLEN], path[MAXPATHLEN];
255 int i;
256 int insertPositions[MAX_PANES], topLines[MAX_PANES];
257 int horizOffsets[MAX_PANES];
258 int openFlags = 0;
259 Widget text;
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",
266 window->filename);
267 return;
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)
289 safeClose(window);
290 else {
291 /* Treat it like an externally modified file */
292 window->lastModTime=0;
293 window->fileMissing=FALSE;
295 return;
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;
318 while(p) {
319 if (p == window) {
320 CloseWindow(window);
321 return;
323 p = p->next;
327 static int doOpen(WindowInfo *window, const char *name, const char *path,
328 int flags)
330 char fullname[MAXPATHLEN];
331 struct stat statbuf;
332 int fileLen, readLen;
333 char *fileString, *c;
334 FILE *fp = NULL;
335 int fd;
336 int resp;
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);
351 /* Open the file */
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);
362 #else
363 fp = fopen(fullname, "rb+");
364 if (fp == NULL) {
365 /* Error opening file or file is not writeable */
366 fp = fopen(fullname, "rb");
367 if (fp != NULL) {
368 /* File is read only */
369 SET_PERM_LOCKED(window->lockReasons, TRUE);
370 #endif
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());
384 else {
385 resp = DialogF(DF_WARN, window->shell, 2, "New File",
386 "Can't open %s:\n%s", "New File", "Cancel", fullname,
387 errorString());
390 if (resp == 2) {
391 return FALSE;
393 else if (resp == 3) {
394 exit(EXIT_SUCCESS);
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());
402 return FALSE;
404 else {
405 #ifdef VMS
406 /* get correct version number and close before removing */
407 getname(fd, fullname);
408 #endif
409 close(fd);
410 remove(fullname);
413 SetWindowModified(window, FALSE);
414 if ((flags & PREF_READ_ONLY) != 0) {
415 SET_USER_LOCKED(window->lockReasons, TRUE);
417 UpdateWindowReadOnly(window);
418 return TRUE;
420 else {
421 /* A true error */
422 DialogF(DF_ERR, window->shell, 1, "Error opening File",
423 "Could not open %s%s:\n%s", "OK", path, name,
424 errorString());
425 return FALSE;
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) {
432 fclose(fp);
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;
437 return FALSE;
440 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)) {
451 fclose(fp);
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;
456 return FALSE;
458 #endif
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) {
464 fclose(fp);
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;
469 return FALSE;
472 /* Read the file into fileString and terminate with a null */
473 readLen = fread(fileString, sizeof(char), fileLen, fp);
474 if (ferror(fp)) {
475 fclose(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;
480 free(fileString);
481 return FALSE;
483 fileString[readLen] = 0;
485 /* Close the file */
486 if (fclose(fp) != 0) {
487 /* unlikely error */
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",
522 "Cancel");
523 if (resp == 2) {
524 return FALSE;
527 SET_TMBD_LOCKED(window->lockReasons, TRUE);
528 for (c = fileString; c < &fileString[readLen]; c++) {
529 if (*c == '\0') {
530 *c = (char) 0xfe;
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 */
541 free(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);
550 } else {
551 SetWindowModified(window, FALSE);
552 if (IS_ANY_LOCKED(window->lockReasons)) {
553 UpdateWindowTitle(window);
556 UpdateWindowReadOnly(window);
558 return TRUE;
561 int IncludeFile(WindowInfo *window, const char *name)
563 struct stat statbuf;
564 int fileLen, readLen;
565 char *fileString;
566 FILE *fp = NULL;
568 /* Open the file */
569 fp = fopen(name, "rb");
570 if (fp == NULL)
572 DialogF(DF_ERR, window->shell, 1, "Error opening File",
573 "Could not open %s:\n%s", "OK", name, errorString());
574 return FALSE;
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);
582 fclose(fp);
583 return FALSE;
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);
590 fclose(fp);
591 return FALSE;
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");
601 fclose(fp);
602 return FALSE;
605 /* read the file into fileString and terminate with a null */
606 readLen = fread(fileString, sizeof(char), fileLen, fp);
607 if (ferror(fp))
609 DialogF(DF_ERR, window->shell, 1, "Error opening File",
610 "Error reading %s:\n%s", "OK", name, errorString());
611 fclose(fp);
612 free(fileString);
613 return FALSE;
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");
632 /* close the file */
633 if (fclose(fp) != 0)
635 /* unlikely error */
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);
645 else
646 BufInsert(window->buffer, TextGetCursorPos(window->lastFocus),
647 fileString);
649 /* release the memory that holds fileString */
650 free(fileString);
652 return TRUE;
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
669 * Untitled.)
671 if (WindowList == MacroRunWindow() && WindowList->next != NULL) {
672 if (!CloseAllDocumentInWindow(WindowList->next)) {
673 return False;
676 else {
677 if (!CloseAllDocumentInWindow(WindowList)) {
678 return False;
683 return TRUE;
686 int CloseFileAndWindow(WindowInfo *window, int preResponse)
688 int response, stat;
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 &&
698 /* Normal File */
699 ((!window->fileMissing && window->lastModTime > 0) ||
700 /* New File*/
701 (window->fileMissing && window->lastModTime == 0) ||
702 /* File deleted/modified externally, ignored by user. */
703 !GetPrefWarnFileMods()))
705 CloseWindow(window);
706 /* up-to-date windows don't have outstanding backup files to close */
707 } else
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);
713 } else
715 response = preResponse;
718 if (response == YES_SBC_DIALOG_RESPONSE)
720 /* Save */
721 stat = SaveWindow(window);
722 if (stat)
724 CloseWindow(window);
725 } else
727 return FALSE;
729 } else if (response == NO_SBC_DIALOG_RESPONSE)
731 /* Don't Save */
732 RemoveBackupFile(window);
733 CloseWindow(window);
734 } else /* 3 == Cancel */
736 return FALSE;
739 return TRUE;
742 int SaveWindow(WindowInfo *window)
744 int stat;
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
750 read-only file. */
751 if ( (!window->fileChanged && !window->fileMissing &&
752 window->lastModTime > 0) ||
753 IS_ANY_LOCKED_IGNORING_PERM(window->lockReasons))
754 return TRUE;
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);
771 if (stat == 2)
773 /* Cancel and mark file as externally modified */
774 window->lastModTime = 0;
775 window->fileMissing = FALSE;
776 return FALSE;
780 #ifdef VMS
781 RemoveBackupFile(window);
782 stat = doSave(window);
783 #else
784 if (writeBckVersion(window))
785 return FALSE;
786 stat = doSave(window);
787 if (stat)
788 RemoveBackupFile(window);
789 #endif /*VMS*/
790 return stat;
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)
804 return FALSE;
805 window->fileFormat = fileFormat;
806 } else
808 strcpy(fullname, newName);
811 if (1 == NormalizePathname(fullname))
813 return False;
816 /* Add newlines if requested */
817 if (addWrap)
818 addWrapNewlines(window);
820 if (ParseFilename(fullname, filename, pathname) != 0) {
821 return FALSE;
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))
828 return FALSE;
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);
843 if (response == 1)
845 return FALSE;
847 if (otherWindow == FindWindowWithFile(filename, pathname))
849 if (!CloseFileAndWindow(otherWindow, PROMPT_SBC_DIALOG_RESPONSE))
851 return FALSE;
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);
880 SortTabBar(window);
881 return retVal;
884 static int doSave(WindowInfo *window)
886 char *fileString = NULL;
887 char fullname[MAXPATHLEN];
888 struct stat statbuf;
889 FILE *fp;
890 int fileLen, result;
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. */
898 if ((0 == getuid())
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);
906 if (1 != result)
908 return True;
912 #ifdef VMS
913 /* strip the version number from the file so VMS will begin a new one */
914 removeVersionNumber(fullname);
915 #endif
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");
931 /* open the file */
932 #ifdef VMS
933 fp = fopen(fullname, "w", "rfm = stmlf");
934 #else
935 fp = fopen(fullname, "wb");
936 #endif /* VMS */
937 if (fp == NULL)
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());
944 if (result == 1)
946 return SaveWindowAs(window, NULL, 0);
948 return FALSE;
951 #ifdef VMS
952 /* get the complete name of the file including the new version number */
953 fgetname(fp, fullname);
954 #endif
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");
970 return FALSE;
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);
980 #else
981 fwrite(fileString, sizeof(char), fileLen, fp);
982 #endif
983 if (ferror(fp))
985 DialogF(DF_ERR, window->shell, 1, "Error saving File",
986 "%s not saved:\n%s", "OK", window->filename, errorString());
987 fclose(fp);
988 remove(fullname);
989 XtFree(fileString);
990 return FALSE;
993 /* close the file */
994 if (fclose(fp) != 0)
996 DialogF(DF_ERR, window->shell, 1, "Error closing File",
997 "Error closing file:\n%s", "OK", errorString());
998 XtFree(fileString);
999 return FALSE;
1002 /* free the text buffer copy returned from XmTextGetString */
1003 XtFree(fileString);
1005 #ifdef VMS
1006 /* reflect the fact that NEdit is now editing a new version of the file */
1007 ParseFilename(fullname, window->filename, window->path);
1008 #endif /*VMS*/
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;
1017 } else {
1018 /* This needs to produce an error message -- the file can't be
1019 accessed! */
1020 window->lastModTime = 0;
1021 window->fileMissing = TRUE;
1024 return 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];
1036 FILE *fp;
1037 int fd, fileLen;
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. */
1044 remove(name);
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 */
1049 #ifdef VMS
1050 if ((fp = fopen(name, "w", "rfm = stmlf")) == NULL)
1051 #else
1052 if ((fd = open(name, O_CREAT|O_EXCL|O_WRONLY, S_IRUSR | S_IWUSR)) < 0
1053 || (fp = fdopen(fd, "w")) == NULL)
1054 #endif /* VMS */
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,
1059 errorString());
1060 window->autoSave = FALSE;
1061 SetToggleButtonState(window, window->autoSaveItem, FALSE, FALSE);
1062 return FALSE;
1065 /* Set VMS permissions */
1066 #ifdef VMS
1067 chmod(name, S_IRUSR | S_IWUSR);
1068 #endif
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);
1084 #else
1085 fwrite(fileString, sizeof(char), fileLen, fp);
1086 #endif
1087 if (ferror(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,
1092 errorString());
1093 fclose(fp);
1094 remove(name);
1095 XtFree(fileString);
1096 window->autoSave = FALSE;
1097 return FALSE;
1100 /* close the backup file */
1101 if (fclose(fp) != 0) {
1102 XtFree(fileString);
1103 return FALSE;
1106 /* Free the text buffer copy returned from XmTextGetString */
1107 XtFree(fileString);
1109 return TRUE;
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)
1121 return;
1123 backupFileName(window, name, sizeof(name));
1124 remove(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];
1134 #ifdef VMS
1135 if (window->filenameSet)
1136 sprintf(name, "%s_%s", window->path, window->filename);
1137 else
1138 sprintf(name, "%s_%s", "SYS$LOGIN:", window->filename);
1139 #else
1140 if (window->filenameSet)
1142 sprintf(name, "%s~%s", window->path, window->filename);
1143 } else
1145 strcpy(bckname, "~");
1146 strncat(bckname, window->filename, MAXPATHLEN - 1);
1147 PrependHome(bckname, name, len);
1149 #endif /*VMS*/
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)
1159 #ifndef VMS
1160 char fullname[MAXPATHLEN], bckname[MAXPATHLEN];
1161 struct stat statbuf;
1162 FILE *inFP, *outFP;
1163 int fd, fileLen;
1164 char *fileString;
1166 /* Do only if version backups are turned on */
1167 if (!window->saveOldVersion) {
1168 return False;
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. */
1184 unlink(bckname);
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");
1189 if (inFP == NULL) {
1190 return FALSE;
1193 /* find the length of the file */
1194 if (fstat(fileno(inFP), &statbuf) != 0) {
1195 return FALSE;
1197 fileLen = statbuf.st_size;
1199 /* open the file exclusive and with restrictive permissions. */
1200 #ifdef VMS
1201 if ((outFP = fopen(bckname, "w", "rfm = stmlf")) == NULL) {
1202 #else
1203 if ((fd = open(bckname, O_CREAT|O_EXCL|O_WRONLY, S_IRUSR | S_IWUSR)) < 0
1204 || (outFP = fdopen(fd, "wb")) == NULL) {
1205 #endif /* VMS */
1206 fclose(inFP);
1207 return bckError(window, "Error open backup file", bckname);
1209 #ifdef VMS
1210 chmod(bckname, S_IRUSR | S_IWUSR);
1211 #endif
1213 /* Allocate space for the whole contents of the file */
1214 fileString = (char *)malloc(fileLen);
1215 if (fileString == NULL) {
1216 fclose(inFP);
1217 fclose(outFP);
1218 return bckError(window, "out of memory", bckname);
1221 /* read the file into fileString */
1222 fread(fileString, sizeof(char), fileLen, inFP);
1223 if (ferror(inFP)) {
1224 fclose(inFP);
1225 fclose(outFP);
1226 free(fileString);
1227 return FALSE;
1230 /* close the input file, ignore any errors */
1231 fclose(inFP);
1233 /* write to the file */
1234 #ifdef IBM_FWRITE_BUG
1235 write(fileno(outFP), fileString, fileLen);
1236 #else
1237 fwrite(fileString, sizeof(char), fileLen, outFP);
1238 #endif
1239 if (ferror(outFP)) {
1240 fclose(outFP);
1241 remove(bckname);
1242 free(fileString);
1243 return bckError(window, errorString(), bckname);
1245 free(fileString);
1247 /* close the file */
1248 if (fclose(outFP) != 0)
1249 return bckError(window, errorString(), bckname);
1250 #endif /* VMS */
1252 return FALSE;
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)
1261 int resp;
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);
1266 if (resp == 1)
1267 return TRUE;
1268 if (resp == 2) {
1269 window->saveOldVersion = FALSE;
1270 #ifndef VMS
1271 SetToggleButtonState(window, window->saveLastItem, FALSE, FALSE);
1272 #endif
1274 return FALSE;
1277 void PrintWindow(WindowInfo *window, int selectedOnly)
1279 textBuffer *buf = window->buffer;
1280 selection *sel = &buf->primary;
1281 char *fileString = NULL;
1282 int fileLen;
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 */
1286 if (selectedOnly) {
1287 if (!sel->selected) {
1288 XBell(TheDisplay, 0);
1289 return;
1291 if (sel->rectangular) {
1292 fileString = BufGetSelectionText(buf);
1293 fileLen = strlen(fileString);
1294 } else
1295 fileString = TextGetWrapped(window->textArea, sel->start, sel->end,
1296 &fileLen);
1297 } else
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 */
1311 XtFree(fileString);
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 */
1321 FILE *fp;
1322 int fd;
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 */
1334 #ifdef VMS
1335 if ((fp = fopen(tmpFileName, "w", "rfm = stmlf")) == NULL)
1336 #else
1337 if ((fd = open(tmpFileName, O_CREAT|O_EXCL|O_WRONLY, S_IRUSR | S_IWUSR)) < 0 || (fp = fdopen(fd, "w")) == NULL)
1338 #endif /* VMS */
1340 DialogF(DF_WARN, parent, 1, "Error while Printing",
1341 "Unable to write file for printing:\n%s", "OK",
1342 errorString());
1343 return;
1346 #ifdef VMS
1347 chmod(tmpFileName, S_IRUSR | S_IWUSR);
1348 #endif
1350 /* write to the file */
1351 #ifdef IBM_FWRITE_BUG
1352 write(fileno(fp), string, length);
1353 #else
1354 fwrite(string, sizeof(char), length, fp);
1355 #endif
1356 if (ferror(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);
1362 return;
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",
1370 errorString());
1371 remove(tmpFileName);
1372 return;
1375 /* Print the temporary file, then delete it and return success */
1376 #ifdef VMS
1377 strcat(tmpFileName, ".");
1378 PrintFile(parent, tmpFileName, jobName, True);
1379 #else
1380 PrintFile(parent, tmpFileName, jobName);
1381 remove(tmpFileName);
1382 #endif /*VMS*/
1383 return;
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;
1393 int retVal;
1395 /* Temporarily set default directory to window->path, prompt for file,
1396 then, if the call was unsuccessful, restore the original default
1397 directory */
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);
1406 return retVal;
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)
1417 int n, retVal;
1418 Arg args[20];
1419 XmString s1, s2;
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
1428 directory */
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 */
1435 n = 0;
1436 XtSetArg(args[n],
1437 XmNselectionLabelString,
1438 s1 = XmStringCreateLocalized("New File Name:")); n++;
1439 XtSetArg(args[n], XmNdialogStyle, XmDIALOG_FULL_APPLICATION_MODAL); n++;
1440 XtSetArg(args[n],
1441 XmNdialogTitle,
1442 s2 = XmStringCreateSimple(prompt)); n++;
1443 fileSB = CreateFileSelectionDialog(window->shell,"FileSelect",args,n);
1444 XmStringFree(s1);
1445 XmStringFree(s2);
1446 formatForm = XtVaCreateManagedWidget("formatForm", xmFormWidgetClass,
1447 fileSB, NULL);
1448 formatBtns = XtVaCreateManagedWidget("formatBtns", xmRowColumnWidgetClass,
1449 formatForm,
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);
1457 XmStringFree(s1);
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);
1465 XmStringFree(s1);
1466 XtAddCallback(unixFormat, XmNvalueChangedCallback, setFormatCB,
1467 fileFormat);
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);
1475 XmStringFree(s1);
1476 XtAddCallback(dosFormat, XmNvalueChangedCallback, setFormatCB,
1477 fileFormat);
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);
1485 XmStringFree(s1);
1486 XtAddCallback(macFormat, XmNvalueChangedCallback, setFormatCB,
1487 fileFormat);
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,
1493 XmNmnemonic, 'A',
1494 XmNtopAttachment, XmATTACH_WIDGET,
1495 XmNtopWidget, formatBtns,
1496 XmNleftAttachment, XmATTACH_FORM, NULL);
1497 XtAddCallback(wrapToggle, XmNvalueChangedCallback, addWrapCB,
1498 addWrap);
1499 XmStringFree(s1);
1501 *addWrap = False;
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);
1525 return retVal;
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)
1535 WindowInfo *w;
1536 int i;
1538 for (i=0; i<INT_MAX; i++) {
1539 if (i == 0)
1540 sprintf(name, "Untitled");
1541 else
1542 sprintf(name, "Untitled_%d", i);
1543 for (w=WindowList; w!=NULL; w=w->next)
1544 if (!strcmp(w->filename, name))
1545 break;
1546 if (w == NULL)
1547 break;
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;
1561 Time timestamp;
1562 FILE *fp;
1563 int resp, silent = 0;
1564 XWindowAttributes winAttr;
1566 if(!window->filenameSet)
1567 return;
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)
1573 return;
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))
1589 silent = 1;
1590 else {
1591 XGetWindowAttributes(XtDisplay(window->shell),
1592 XtWindow(window->shell),
1593 &winAttr);
1595 if (winAttr.map_state != IsViewable)
1596 silent = 1;
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)
1605 return;
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);
1622 else
1623 resp = DialogF(DF_ERR, window->shell, 3, "File not found",
1624 "Error while checking the status of file \"%s\":\n"
1625 " \"%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,
1629 errorString());
1630 if (resp == 1)
1631 SaveWindow(window);
1632 else if (resp == 2)
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);
1639 return;
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) {
1647 int readOnly;
1648 fclose(fp);
1649 #ifndef DONT_USE_ACCESS
1650 readOnly = access(fullname, W_OK) != 0;
1651 #else
1652 if (((fp = fopen(fullname, "r+")) != NULL)) {
1653 readOnly = FALSE;
1654 fclose(fp);
1655 } else
1656 readOnly = TRUE;
1657 #endif
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. */
1673 if (!silent &&
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())
1680 return;
1681 if (GetPrefWarnRealFileMods() &&
1682 !cmpWinAgainstFile(window, fullname)) {
1683 /* Contents hasn't changed. Update the modification time. */
1684 window->lastModTime = statbuf.st_mtime;
1685 return;
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);
1694 else
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);
1699 if (resp == 1)
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
1707 ** unavailable.
1709 static int fileWasModifiedExternally(WindowInfo *window)
1711 char fullname[MAXPATHLEN];
1712 struct stat statbuf;
1714 if(!window->filenameSet)
1715 return FALSE;
1716 /* if (window->lastModTime == 0)
1717 return FALSE; */
1718 strcpy(fullname, window->path);
1719 strcat(fullname, window->filename);
1720 if (stat(fullname, &statbuf) != 0)
1721 return FALSE;
1722 if (window->lastModTime == statbuf.st_mtime)
1723 return FALSE;
1724 if (GetPrefWarnRealFileMods() &&
1725 !cmpWinAgainstFile(window, fullname)) {
1726 return FALSE;
1728 return TRUE;
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);
1739 return True;
1741 return False;
1745 ** Wrapper for strerror so all the calls don't have to be ifdef'd for VMS.
1747 static const char *errorString(void)
1749 #ifdef VMS
1750 return strerror(errno, vaxc$errno);
1751 #else
1752 return strerror(errno);
1753 #endif
1756 #ifdef VMS
1758 ** Removing the VMS version number from a file name (if has one).
1760 void removeVersionNumber(char *fileName)
1762 char *versionStart;
1764 versionStart = strrchr(fileName, ';');
1765 if (versionStart != NULL)
1766 *versionStart = '\0';
1768 #endif /*VMS*/
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)
1786 int resp;
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",
1798 "Cancel");
1799 if (resp == 2)
1801 XmToggleButtonSetState(w, False, False);
1802 *addWrap = False;
1803 } else
1805 *addWrap = True;
1807 } else
1809 *addWrap = 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];
1821 int horizOffset;
1822 Widget text;
1823 char *fileString;
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);
1836 XtFree(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.
1860 * Return values
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;
1869 char pendingCR = 0;
1870 int fileFormat = window->fileFormat;
1871 char message[MAXPATHLEN+50];
1872 textBuffer *buf = window->buffer;
1873 FILE *fp;
1875 fp = fopen(fileName, "r");
1876 if (!fp)
1877 return (1);
1878 if (fstat(fileno(fp), &statbuf) != 0) {
1879 fclose(fp);
1880 return (1);
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) {
1887 fclose(fp);
1888 return (1);
1890 } else {
1891 /* If a DOS file is smaller on disk, it's certainly different */
1892 if (fileLen < buf->length) {
1893 fclose(fp);
1894 return (1);
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);
1902 bufPos = 0;
1903 filePos = 0;
1904 while (restLen > 0) {
1905 AllWindowsBusy(message);
1906 if (pendingCR) {
1907 fileString[0] = pendingCR;
1908 offset = 1;
1909 } else {
1910 offset = 0;
1913 nRead = fread(fileString+offset, sizeof(char), restLen, fp);
1914 if (nRead != restLen) {
1915 fclose(fp);
1916 AllWindowsUnbusy();
1917 return (1);
1919 filePos += nRead;
1921 nRead += offset;
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);
1931 if (rv) {
1932 fclose(fp);
1933 AllWindowsUnbusy();
1934 return (rv);
1936 bufPos += nRead;
1937 restLen = min(fileLen - filePos, PREFERRED_CMPBUF_LEN);
1939 AllWindowsUnbusy();
1940 fclose(fp);
1941 if (pendingCR) {
1942 rv = BufCmp(buf, bufPos, 1, &pendingCR);
1943 if (rv) {
1944 return (rv);
1946 bufPos += 1;
1948 if (bufPos != buf->length) {
1949 return (1);
1951 return (0);
1954 static int min(int i1, int i2)
1956 return i1 <= i2 ? i1 : i2;