Fix for SF bug #1122813: Tab-related crash on posting unload calltips file
[nedit.git] / source / file.c
blob0323e0ae394e968ae774b548d9bdc8b6bad2e0c3
1 static const char CVSID[] = "$Id: file.c,v 1.93 2004/12/23 22:25:45 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 #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)) {
373 /* on Solaris 2.6, and possibly other OSes, dialog won't
374 show if parent window is iconized. */
375 RaiseShellWindow(window->shell, False);
377 /* ask user for next action if file not found */
378 if (WindowList == window && window->next == NULL) {
379 resp = DialogF(DF_WARN, window->shell, 3, "New File",
380 "Can't open %s:\n%s", "New File", "Cancel",
381 "Exit NEdit", fullname, errorString());
383 else {
384 resp = DialogF(DF_WARN, window->shell, 2, "New File",
385 "Can't open %s:\n%s", "New File", "Cancel", fullname,
386 errorString());
389 if (resp == 2) {
390 return FALSE;
392 else if (resp == 3) {
393 exit(EXIT_SUCCESS);
397 /* Test if new file can be created */
398 if ((fd = creat(fullname, 0666)) == -1) {
399 DialogF(DF_ERR, window->shell, 1, "Error creating File",
400 "Can't create %s:\n%s", "OK", fullname, errorString());
401 return FALSE;
403 else {
404 #ifdef VMS
405 /* get correct version number and close before removing */
406 getname(fd, fullname);
407 #endif
408 close(fd);
409 remove(fullname);
412 SetWindowModified(window, FALSE);
413 if ((flags & PREF_READ_ONLY) != 0) {
414 SET_USER_LOCKED(window->lockReasons, TRUE);
416 UpdateWindowReadOnly(window);
417 return TRUE;
419 else {
420 /* A true error */
421 DialogF(DF_ERR, window->shell, 1, "Error opening File",
422 "Could not open %s%s:\n%s", "OK", path, name,
423 errorString());
424 return FALSE;
428 /* Get the length of the file, the protection mode, and the time of the
429 last modification to the file */
430 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)) {
440 fclose(fp);
441 window->filenameSet = FALSE; /* Temp. prevent check for changes. */
442 DialogF(DF_ERR, window->shell, 1, "Error opening File",
443 "Can't open directory %s", "OK", name);
444 window->filenameSet = TRUE;
445 return FALSE;
448 #ifdef S_ISBLK
449 if (S_ISBLK(statbuf.st_mode)) {
450 fclose(fp);
451 window->filenameSet = FALSE; /* Temp. prevent check for changes. */
452 DialogF(DF_ERR, window->shell, 1, "Error opening File",
453 "Can't open block device %s", "OK", name);
454 window->filenameSet = TRUE;
455 return FALSE;
457 #endif
458 fileLen = statbuf.st_size;
460 /* Allocate space for the whole contents of the file (unfortunately) */
461 fileString = (char *)malloc(fileLen+1); /* +1 = space for null */
462 if (fileString == NULL) {
463 fclose(fp);
464 window->filenameSet = FALSE; /* Temp. prevent check for changes. */
465 DialogF(DF_ERR, window->shell, 1, "Error while opening File",
466 "File is too large to edit", "OK");
467 window->filenameSet = TRUE;
468 return FALSE;
471 /* Read the file into fileString and terminate with a null */
472 readLen = fread(fileString, sizeof(char), fileLen, fp);
473 if (ferror(fp)) {
474 fclose(fp);
475 window->filenameSet = FALSE; /* Temp. prevent check for changes. */
476 DialogF(DF_ERR, window->shell, 1, "Error while opening File",
477 "Error reading %s:\n%s", "OK", name, errorString());
478 window->filenameSet = TRUE;
479 free(fileString);
480 return FALSE;
482 fileString[readLen] = 0;
484 /* Close the file */
485 if (fclose(fp) != 0) {
486 /* unlikely error */
487 DialogF(DF_WARN, window->shell, 1, "Error while opening File",
488 "Unable to close file", "OK");
489 /* we read it successfully, so continue */
492 /* Any errors that happen after this point leave the window in a
493 "broken" state, and thus RevertToSaved will abandon the window if
494 window->fileMissing is FALSE and doOpen fails. */
495 window->fileMode = statbuf.st_mode;
496 window->lastModTime = statbuf.st_mtime;
497 window->fileMissing = FALSE;
498 /* Detect and convert DOS and Macintosh format files */
499 window->fileFormat = FormatOfFile(fileString);
500 if (window->fileFormat == DOS_FILE_FORMAT) {
501 ConvertFromDosFileString(fileString, &readLen, NULL);
503 else if (window->fileFormat == MAC_FILE_FORMAT) {
504 ConvertFromMacFileString(fileString, readLen);
507 /* Display the file contents in the text widget */
508 window->ignoreModify = True;
509 BufSetAll(window->buffer, fileString);
510 window->ignoreModify = False;
512 /* Check that the length that the buffer thinks it has is the same
513 as what we gave it. If not, there were probably nuls in the file.
514 Substitute them with another character. If that is impossible, warn
515 the user, make the file read-only, and force a substitution */
516 if (window->buffer->length != readLen) {
517 if (!BufSubstituteNullChars(fileString, readLen, window->buffer)) {
518 resp = DialogF(DF_ERR, window->shell, 2, "Error while opening File",
519 "Too much binary data in file. You may view\n"
520 "it, but not modify or re-save its contents.", "View",
521 "Cancel");
522 if (resp == 2) {
523 return FALSE;
526 SET_TMBD_LOCKED(window->lockReasons, TRUE);
527 for (c = fileString; c < &fileString[readLen]; c++) {
528 if (*c == '\0') {
529 *c = (char) 0xfe;
532 window->buffer->nullSubsChar = (char) 0xfe;
534 window->ignoreModify = True;
535 BufSetAll(window->buffer, fileString);
536 window->ignoreModify = False;
539 /* Release the memory that holds fileString */
540 free(fileString);
542 /* Set window title and file changed flag */
543 if ((flags & PREF_READ_ONLY) != 0) {
544 SET_USER_LOCKED(window->lockReasons, TRUE);
546 if (IS_PERM_LOCKED(window->lockReasons)) {
547 window->fileChanged = FALSE;
548 UpdateWindowTitle(window);
549 } else {
550 SetWindowModified(window, FALSE);
551 if (IS_ANY_LOCKED(window->lockReasons)) {
552 UpdateWindowTitle(window);
555 UpdateWindowReadOnly(window);
557 return TRUE;
560 int IncludeFile(WindowInfo *window, const char *name)
562 struct stat statbuf;
563 int fileLen, readLen;
564 char *fileString;
565 FILE *fp = NULL;
567 /* Open the file */
568 fp = fopen(name, "rb");
569 if (fp == NULL)
571 DialogF(DF_ERR, window->shell, 1, "Error opening File",
572 "Could not open %s:\n%s", "OK", name, errorString());
573 return FALSE;
576 /* Get the length of the file */
577 if (fstat(fileno(fp), &statbuf) != 0)
579 DialogF(DF_ERR, window->shell, 1, "Error opening File",
580 "Error opening %s", "OK", name);
581 fclose(fp);
582 return FALSE;
585 if (S_ISDIR(statbuf.st_mode))
587 DialogF(DF_ERR, window->shell, 1, "Error opening File",
588 "Can't open directory %s", "OK", name);
589 fclose(fp);
590 return FALSE;
592 fileLen = statbuf.st_size;
594 /* allocate space for the whole contents of the file */
595 fileString = (char *)malloc(fileLen+1); /* +1 = space for null */
596 if (fileString == NULL)
598 DialogF(DF_ERR, window->shell, 1, "Error opening File",
599 "File is too large to include", "OK");
600 fclose(fp);
601 return FALSE;
604 /* read the file into fileString and terminate with a null */
605 readLen = fread(fileString, sizeof(char), fileLen, fp);
606 if (ferror(fp))
608 DialogF(DF_ERR, window->shell, 1, "Error opening File",
609 "Error reading %s:\n%s", "OK", name, errorString());
610 fclose(fp);
611 free(fileString);
612 return FALSE;
614 fileString[readLen] = 0;
616 /* Detect and convert DOS and Macintosh format files */
617 switch (FormatOfFile(fileString)) {
618 case DOS_FILE_FORMAT:
619 ConvertFromDosFileString(fileString, &readLen, NULL); break;
620 case MAC_FILE_FORMAT:
621 ConvertFromMacFileString(fileString, readLen); break;
624 /* If the file contained ascii nulls, re-map them */
625 if (!BufSubstituteNullChars(fileString, readLen, window->buffer))
627 DialogF(DF_ERR, window->shell, 1, "Error opening File",
628 "Too much binary data in file", "OK");
631 /* close the file */
632 if (fclose(fp) != 0)
634 /* unlikely error */
635 DialogF(DF_WARN, window->shell, 1, "Error opening File",
636 "Unable to close file", "OK");
637 /* we read it successfully, so continue */
640 /* insert the contents of the file in the selection or at the insert
641 position in the window if no selection exists */
642 if (window->buffer->primary.selected)
643 BufReplaceSelected(window->buffer, fileString);
644 else
645 BufInsert(window->buffer, TextGetCursorPos(window->lastFocus),
646 fileString);
648 /* release the memory that holds fileString */
649 free(fileString);
651 return TRUE;
655 ** Close all files and windows, leaving one untitled window
657 int CloseAllFilesAndWindows(void)
659 while (WindowList->next != NULL ||
660 WindowList->filenameSet || WindowList->fileChanged) {
662 * When we're exiting through a macro, the document running the
663 * macro does not disappear from the list, so we could get stuck
664 * in an endless loop if we try to close it. Therefore, we close
665 * other documents first. (Note that the document running the macro
666 * may get closed because it is in the same window as another
667 * document that gets closed, but it won't disappear; it becomes
668 * Untitled.)
670 if (WindowList == MacroRunWindow() && WindowList->next != NULL) {
671 if (!CloseAllDocumentInWindow(WindowList->next)) {
672 return False;
675 else {
676 if (!CloseAllDocumentInWindow(WindowList)) {
677 return False;
682 return TRUE;
685 int CloseFileAndWindow(WindowInfo *window, int preResponse)
687 int response, stat;
689 /* Make sure that the window is not in iconified state */
690 if (window->fileChanged)
691 RaiseDocumentWindow(window);
693 /* If the window is a normal & unmodified file or an empty new file,
694 or if the user wants to ignore external modifications then
695 just close it. Otherwise ask for confirmation first. */
696 if (!window->fileChanged &&
697 /* Normal File */
698 ((!window->fileMissing && window->lastModTime > 0) ||
699 /* New File*/
700 (window->fileMissing && window->lastModTime == 0) ||
701 /* File deleted/modified externally, ignored by user. */
702 !GetPrefWarnFileMods()))
704 CloseWindow(window);
705 /* up-to-date windows don't have outstanding backup files to close */
706 } else
708 if (preResponse == PROMPT_SBC_DIALOG_RESPONSE)
710 response = DialogF(DF_WARN, window->shell, 3, "Save File",
711 "Save %s before closing?", "Yes", "No", "Cancel", window->filename);
712 } else
714 response = preResponse;
717 if (response == YES_SBC_DIALOG_RESPONSE)
719 /* Save */
720 stat = SaveWindow(window);
721 if (stat)
723 CloseWindow(window);
724 } else
726 return FALSE;
728 } else if (response == NO_SBC_DIALOG_RESPONSE)
730 /* Don't Save */
731 RemoveBackupFile(window);
732 CloseWindow(window);
733 } else /* 3 == Cancel */
735 return FALSE;
738 return TRUE;
741 int SaveWindow(WindowInfo *window)
743 int stat;
745 /* Try to ensure our information is up-to-date */
746 CheckForChangesToFile(window);
748 /* Return success if the file is normal & unchanged or is a
749 read-only file. */
750 if ( (!window->fileChanged && !window->fileMissing &&
751 window->lastModTime > 0) ||
752 IS_ANY_LOCKED_IGNORING_PERM(window->lockReasons))
753 return TRUE;
754 /* Prompt for a filename if this is an Untitled window */
755 if (!window->filenameSet)
756 return SaveWindowAs(window, NULL, False);
758 /* Check for external modifications and warn the user */
759 if (GetPrefWarnFileMods() && fileWasModifiedExternally(window))
761 stat = DialogF(DF_WARN, window->shell, 2, "Save File",
762 "%s has been modified by another program.\n\n"
763 "Continuing this operation will overwrite any external\n"
764 "modifications to the file since it was opened in NEdit,\n"
765 "and your work or someone else's may potentially be lost.\n\n"
766 "To preserve the modified file, cancel this operation and\n"
767 "use Save As... to save this file under a different name,\n"
768 "or Revert to Saved to revert to the modified version.",
769 "Continue", "Cancel", window->filename);
770 if (stat == 2)
772 /* Cancel and mark file as externally modified */
773 window->lastModTime = 0;
774 window->fileMissing = FALSE;
775 return FALSE;
779 #ifdef VMS
780 RemoveBackupFile(window);
781 stat = doSave(window);
782 #else
783 if (writeBckVersion(window))
784 return FALSE;
785 stat = doSave(window);
786 if (stat)
787 RemoveBackupFile(window);
788 #endif /*VMS*/
789 return stat;
792 int SaveWindowAs(WindowInfo *window, const char *newName, int addWrap)
794 int response, retVal, fileFormat;
795 char fullname[MAXPATHLEN], filename[MAXPATHLEN], pathname[MAXPATHLEN];
796 WindowInfo *otherWindow;
798 /* Get the new name for the file */
799 if (newName == NULL) {
800 response = PromptForNewFile(window, "Save File As", fullname,
801 &fileFormat, &addWrap);
802 if (response != GFN_OK)
803 return FALSE;
804 window->fileFormat = fileFormat;
805 } else
807 strcpy(fullname, newName);
810 if (1 == NormalizePathname(fullname))
812 return False;
815 /* Add newlines if requested */
816 if (addWrap)
817 addWrapNewlines(window);
819 if (ParseFilename(fullname, filename, pathname) != 0) {
820 return FALSE;
823 /* If the requested file is this file, just save it and return */
824 if (!strcmp(window->filename, filename) &&
825 !strcmp(window->path, pathname)) {
826 if (writeBckVersion(window))
827 return FALSE;
828 return doSave(window);
831 /* If the file is open in another window, make user close it. Note that
832 it is possible for user to close the window by hand while the dialog
833 is still up, because the dialog is not application modal, so after
834 doing the dialog, check again whether the window still exists. */
835 otherWindow = FindWindowWithFile(filename, pathname);
836 if (otherWindow != NULL)
838 response = DialogF(DF_WARN, window->shell, 2, "File open",
839 "%s is open in another NEdit window", "Cancel",
840 "Close Other Window", filename);
842 if (response == 1)
844 return FALSE;
846 if (otherWindow == FindWindowWithFile(filename, pathname))
848 if (!CloseFileAndWindow(otherWindow, PROMPT_SBC_DIALOG_RESPONSE))
850 return FALSE;
855 /* Destroy the file closed property for the original file */
856 DeleteFileClosedProperty(window);
858 /* Change the name of the file and save it under the new name */
859 RemoveBackupFile(window);
860 strcpy(window->filename, filename);
861 strcpy(window->path, pathname);
862 window->filenameSet = TRUE;
863 window->fileMode = 0;
864 CLEAR_ALL_LOCKS(window->lockReasons);
865 retVal = doSave(window);
866 UpdateWindowTitle(window);
867 UpdateWindowReadOnly(window);
868 RefreshTabState(window);
870 /* Add the name to the convenience menu of previously opened files */
871 AddToPrevOpenMenu(fullname);
873 /* If name has changed, language mode may have changed as well */
874 DetermineLanguageMode(window, False);
876 /* Update the stats line with the new filename */
877 UpdateStatsLine(window);
879 SortTabBar(window);
880 return retVal;
883 static int doSave(WindowInfo *window)
885 char *fileString = NULL;
886 char fullname[MAXPATHLEN];
887 struct stat statbuf;
888 FILE *fp;
889 int fileLen, result;
891 /* Get the full name of the file */
892 strcpy(fullname, window->path);
893 strcat(fullname, window->filename);
895 /* Check for root and warn him if he wants to write to a file with
896 none of the write bits set. */
897 if ((0 == getuid())
898 && (0 == stat(fullname, &statbuf))
899 && !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH)))
901 result = DialogF(DF_WARN, window->shell, 2, "Writing Read-only File",
902 "File '%s' is marked as read-only.\n"
903 "Do you want to save anyway?",
904 "Save", "Cancel", window->filename);
905 if (1 != result)
907 return True;
911 #ifdef VMS
912 /* strip the version number from the file so VMS will begin a new one */
913 removeVersionNumber(fullname);
914 #endif
916 /* add a terminating newline if the file doesn't already have one for
917 Unix utilities which get confused otherwise
918 NOTE: this must be done _before_ we create/open the file, because the
919 (potential) buffer modification can trigger a check for file
920 changes. If the file is created for the first time, it has
921 zero size on disk, and the check would falsely conclude that the
922 file has changed on disk, and would pop up a warning dialog */
923 if (BufGetCharacter(window->buffer, window->buffer->length - 1) != '\n'
924 && window->buffer->length != 0
925 && GetPrefAppendLF())
927 BufInsert(window->buffer, window->buffer->length, "\n");
930 /* open the file */
931 #ifdef VMS
932 fp = fopen(fullname, "w", "rfm = stmlf");
933 #else
934 fp = fopen(fullname, "wb");
935 #endif /* VMS */
936 if (fp == NULL)
938 result = DialogF(DF_WARN, window->shell, 2, "Error saving File",
939 "Unable to save %s:\n%s\n\nSave as a new file?",
940 "Save As...", "Cancel",
941 window->filename, errorString());
943 if (result == 1)
945 return SaveWindowAs(window, NULL, 0);
947 return FALSE;
950 #ifdef VMS
951 /* get the complete name of the file including the new version number */
952 fgetname(fp, fullname);
953 #endif
955 /* get the text buffer contents and its length */
956 fileString = BufGetAll(window->buffer);
957 fileLen = window->buffer->length;
959 /* If null characters are substituted for, put them back */
960 BufUnsubstituteNullChars(fileString, window->buffer);
962 /* If the file is to be saved in DOS or Macintosh format, reconvert */
963 if (window->fileFormat == DOS_FILE_FORMAT)
965 if (!ConvertToDosFileString(&fileString, &fileLen))
967 DialogF(DF_ERR, window->shell, 1, "Out of Memory",
968 "Out of memory! Try\nsaving in Unix format", "OK");
969 return FALSE;
971 } else if (window->fileFormat == MAC_FILE_FORMAT)
973 ConvertToMacFileString(fileString, fileLen);
976 /* write to the file */
977 #ifdef IBM_FWRITE_BUG
978 write(fileno(fp), fileString, fileLen);
979 #else
980 fwrite(fileString, sizeof(char), fileLen, fp);
981 #endif
982 if (ferror(fp))
984 DialogF(DF_ERR, window->shell, 1, "Error saving File",
985 "%s not saved:\n%s", "OK", window->filename, errorString());
986 fclose(fp);
987 remove(fullname);
988 XtFree(fileString);
989 return FALSE;
992 /* close the file */
993 if (fclose(fp) != 0)
995 DialogF(DF_ERR, window->shell, 1, "Error closing File",
996 "Error closing file:\n%s", "OK", errorString());
997 XtFree(fileString);
998 return FALSE;
1001 /* free the text buffer copy returned from XmTextGetString */
1002 XtFree(fileString);
1004 #ifdef VMS
1005 /* reflect the fact that NEdit is now editing a new version of the file */
1006 ParseFilename(fullname, window->filename, window->path);
1007 #endif /*VMS*/
1009 /* success, file was written */
1010 SetWindowModified(window, FALSE);
1012 /* update the modification time */
1013 if (stat(fullname, &statbuf) == 0) {
1014 window->lastModTime = statbuf.st_mtime;
1015 window->fileMissing = FALSE;
1016 } else {
1017 /* This needs to produce an error message -- the file can't be
1018 accessed! */
1019 window->lastModTime = 0;
1020 window->fileMissing = TRUE;
1023 return TRUE;
1027 ** Create a backup file for the current window. The name for the backup file
1028 ** is generated using the name and path stored in the window and adding a
1029 ** tilde (~) on UNIX and underscore (_) on VMS to the beginning of the name.
1031 int WriteBackupFile(WindowInfo *window)
1033 char *fileString = NULL;
1034 char name[MAXPATHLEN];
1035 FILE *fp;
1036 int fd, fileLen;
1038 /* Generate a name for the autoSave file */
1039 backupFileName(window, name, sizeof(name));
1041 /* remove the old backup file.
1042 Well, this might fail - we'll notice later however. */
1043 remove(name);
1045 /* open the file, set more restrictive permissions (using default
1046 permissions was somewhat of a security hole, because permissions were
1047 independent of those of the original file being edited */
1048 #ifdef VMS
1049 if ((fp = fopen(name, "w", "rfm = stmlf")) == NULL)
1050 #else
1051 if ((fd = open(name, O_CREAT|O_EXCL|O_WRONLY, S_IRUSR | S_IWUSR)) < 0
1052 || (fp = fdopen(fd, "w")) == NULL)
1053 #endif /* VMS */
1055 DialogF(DF_WARN, window->shell, 1, "Error writing Backup",
1056 "Unable to save backup for %s:\n%s\n"
1057 "Automatic backup is now off", "OK", window->filename,
1058 errorString());
1059 window->autoSave = FALSE;
1060 SetToggleButtonState(window, window->autoSaveItem, FALSE, FALSE);
1061 return FALSE;
1064 /* Set VMS permissions */
1065 #ifdef VMS
1066 chmod(name, S_IRUSR | S_IWUSR);
1067 #endif
1069 /* get the text buffer contents and its length */
1070 fileString = BufGetAll(window->buffer);
1071 fileLen = window->buffer->length;
1073 /* If null characters are substituted for, put them back */
1074 BufUnsubstituteNullChars(fileString, window->buffer);
1076 /* add a terminating newline if the file doesn't already have one */
1077 if (fileLen != 0 && fileString[fileLen-1] != '\n')
1078 fileString[fileLen++] = '\n'; /* null terminator no longer needed */
1080 /* write out the file */
1081 #ifdef IBM_FWRITE_BUG
1082 write(fileno(fp), fileString, fileLen);
1083 #else
1084 fwrite(fileString, sizeof(char), fileLen, fp);
1085 #endif
1086 if (ferror(fp))
1088 DialogF(DF_ERR, window->shell, 1, "Error saving Backup",
1089 "Error while saving backup for %s:\n%s\n"
1090 "Automatic backup is now off", "OK", window->filename,
1091 errorString());
1092 fclose(fp);
1093 remove(name);
1094 XtFree(fileString);
1095 window->autoSave = FALSE;
1096 return FALSE;
1099 /* close the backup file */
1100 if (fclose(fp) != 0) {
1101 XtFree(fileString);
1102 return FALSE;
1105 /* Free the text buffer copy returned from XmTextGetString */
1106 XtFree(fileString);
1108 return TRUE;
1112 ** Remove the backup file associated with this window
1114 void RemoveBackupFile(WindowInfo *window)
1116 char name[MAXPATHLEN];
1118 /* Don't delete backup files when backups aren't activated. */
1119 if (window->autoSave == FALSE)
1120 return;
1122 backupFileName(window, name, sizeof(name));
1123 remove(name);
1127 ** Generate the name of the backup file for this window from the filename
1128 ** and path in the window data structure & write into name
1130 static void backupFileName(WindowInfo *window, char *name, int len)
1132 char bckname[MAXPATHLEN];
1133 #ifdef VMS
1134 if (window->filenameSet)
1135 sprintf(name, "%s_%s", window->path, window->filename);
1136 else
1137 sprintf(name, "%s_%s", "SYS$LOGIN:", window->filename);
1138 #else
1139 if (window->filenameSet)
1141 sprintf(name, "%s~%s", window->path, window->filename);
1142 } else
1144 strcpy(bckname, "~");
1145 strncat(bckname, window->filename, MAXPATHLEN - 1);
1146 PrependHome(bckname, name, len);
1148 #endif /*VMS*/
1152 ** If saveOldVersion is on, copies the existing version of the file to
1153 ** <filename>.bck in anticipation of a new version being saved. Returns
1154 ** True if backup fails and user requests that the new file not be written.
1156 static int writeBckVersion(WindowInfo *window)
1158 #ifndef VMS
1159 char fullname[MAXPATHLEN], bckname[MAXPATHLEN];
1160 struct stat statbuf;
1161 FILE *inFP, *outFP;
1162 int fd, fileLen;
1163 char *fileString;
1165 /* Do only if version backups are turned on */
1166 if (!window->saveOldVersion) {
1167 return False;
1170 /* Get the full name of the file */
1171 strcpy(fullname, window->path);
1172 strcat(fullname, window->filename);
1174 /* Generate name for old version */
1175 if ((int)(strlen(fullname) + 5) > (int)MAXPATHLEN)
1177 return bckError(window, "file name too long", window->filename);
1179 sprintf(bckname, "%s.bck", fullname);
1181 /* Delete the old backup file */
1182 /* Errors are ignored; we'll notice them later. */
1183 unlink(bckname);
1185 /* open the file being edited. If there are problems with the
1186 old file, don't bother the user, just skip the backup */
1187 inFP = fopen(fullname, "rb");
1188 if (inFP == NULL) {
1189 return FALSE;
1192 /* find the length of the file */
1193 if (fstat(fileno(inFP), &statbuf) != 0) {
1194 return FALSE;
1196 fileLen = statbuf.st_size;
1198 /* open the file exclusive and with restrictive permissions. */
1199 #ifdef VMS
1200 if ((outFP = fopen(bckname, "w", "rfm = stmlf")) == NULL) {
1201 #else
1202 if ((fd = open(bckname, O_CREAT|O_EXCL|O_WRONLY, S_IRUSR | S_IWUSR)) < 0
1203 || (outFP = fdopen(fd, "wb")) == NULL) {
1204 #endif /* VMS */
1205 fclose(inFP);
1206 return bckError(window, "Error open backup file", bckname);
1208 #ifdef VMS
1209 chmod(bckname, S_IRUSR | S_IWUSR);
1210 #endif
1212 /* Allocate space for the whole contents of the file */
1213 fileString = (char *)malloc(fileLen);
1214 if (fileString == NULL) {
1215 fclose(inFP);
1216 fclose(outFP);
1217 return bckError(window, "out of memory", bckname);
1220 /* read the file into fileString */
1221 fread(fileString, sizeof(char), fileLen, inFP);
1222 if (ferror(inFP)) {
1223 fclose(inFP);
1224 fclose(outFP);
1225 free(fileString);
1226 return FALSE;
1229 /* close the input file, ignore any errors */
1230 fclose(inFP);
1232 /* write to the file */
1233 #ifdef IBM_FWRITE_BUG
1234 write(fileno(outFP), fileString, fileLen);
1235 #else
1236 fwrite(fileString, sizeof(char), fileLen, outFP);
1237 #endif
1238 if (ferror(outFP)) {
1239 fclose(outFP);
1240 remove(bckname);
1241 free(fileString);
1242 return bckError(window, errorString(), bckname);
1244 free(fileString);
1246 /* close the file */
1247 if (fclose(outFP) != 0)
1248 return bckError(window, errorString(), bckname);
1249 #endif /* VMS */
1251 return FALSE;
1255 ** Error processing for writeBckVersion, gives the user option to cancel
1256 ** the subsequent save, or continue and optionally turn off versioning
1258 static int bckError(WindowInfo *window, const char *errString, const char *file)
1260 int resp;
1262 resp = DialogF(DF_ERR, window->shell, 3, "Error writing Backup",
1263 "Couldn't write .bck (last version) file.\n%s: %s", "Cancel Save",
1264 "Turn off Backups", "Continue", file, errString);
1265 if (resp == 1)
1266 return TRUE;
1267 if (resp == 2) {
1268 window->saveOldVersion = FALSE;
1269 SetToggleButtonState(window, window->saveLastItem, FALSE, FALSE);
1271 return FALSE;
1274 void PrintWindow(WindowInfo *window, int selectedOnly)
1276 textBuffer *buf = window->buffer;
1277 selection *sel = &buf->primary;
1278 char *fileString = NULL;
1279 int fileLen;
1281 /* get the contents of the text buffer from the text area widget. Add
1282 wrapping newlines if necessary to make it match the displayed text */
1283 if (selectedOnly) {
1284 if (!sel->selected) {
1285 XBell(TheDisplay, 0);
1286 return;
1288 if (sel->rectangular) {
1289 fileString = BufGetSelectionText(buf);
1290 fileLen = strlen(fileString);
1291 } else
1292 fileString = TextGetWrapped(window->textArea, sel->start, sel->end,
1293 &fileLen);
1294 } else
1295 fileString = TextGetWrapped(window->textArea, 0, buf->length, &fileLen);
1297 /* If null characters are substituted for, put them back */
1298 BufUnsubstituteNullChars(fileString, buf);
1300 /* add a terminating newline if the file doesn't already have one */
1301 if (fileLen != 0 && fileString[fileLen-1] != '\n')
1302 fileString[fileLen++] = '\n'; /* null terminator no longer needed */
1304 /* Print the string */
1305 PrintString(fileString, fileLen, window->shell, window->filename);
1307 /* Free the text buffer copy returned from XmTextGetString */
1308 XtFree(fileString);
1312 ** Print a string (length is required). parent is the dialog parent, for
1313 ** error dialogs, and jobName is the print title.
1315 void PrintString(const char *string, int length, Widget parent, const char *jobName)
1317 char tmpFileName[L_tmpnam]; /* L_tmpnam defined in stdio.h */
1318 FILE *fp;
1319 int fd;
1321 /* Generate a temporary file name */
1322 /* If the glibc is used, the linker issues a warning at this point. This is
1323 very thoughtful of him, but does not apply to NEdit. The recommended
1324 replacement mkstemp(3) uses the same algorithm as NEdit, namely
1325 1. Create a filename
1326 2. Open the file with the O_CREAT|O_EXCL flags
1327 So all an attacker can do is a DoS on the print function. */
1328 tmpnam(tmpFileName);
1330 /* open the temporary file */
1331 #ifdef VMS
1332 if ((fp = fopen(tmpFileName, "w", "rfm = stmlf")) == NULL)
1333 #else
1334 if ((fd = open(tmpFileName, O_CREAT|O_EXCL|O_WRONLY, S_IRUSR | S_IWUSR)) < 0 || (fp = fdopen(fd, "w")) == NULL)
1335 #endif /* VMS */
1337 DialogF(DF_WARN, parent, 1, "Error while Printing",
1338 "Unable to write file for printing:\n%s", "OK",
1339 errorString());
1340 return;
1343 #ifdef VMS
1344 chmod(tmpFileName, S_IRUSR | S_IWUSR);
1345 #endif
1347 /* write to the file */
1348 #ifdef IBM_FWRITE_BUG
1349 write(fileno(fp), string, length);
1350 #else
1351 fwrite(string, sizeof(char), length, fp);
1352 #endif
1353 if (ferror(fp))
1355 DialogF(DF_ERR, parent, 1, "Error while Printing",
1356 "%s not printed:\n%s", "OK", jobName, errorString());
1357 fclose(fp); /* should call close(fd) in turn! */
1358 remove(tmpFileName);
1359 return;
1362 /* close the temporary file */
1363 if (fclose(fp) != 0)
1365 DialogF(DF_ERR, parent, 1, "Error while Printing",
1366 "Error closing temp. print file:\n%s", "OK",
1367 errorString());
1368 remove(tmpFileName);
1369 return;
1372 /* Print the temporary file, then delete it and return success */
1373 #ifdef VMS
1374 strcat(tmpFileName, ".");
1375 PrintFile(parent, tmpFileName, jobName, True);
1376 #else
1377 PrintFile(parent, tmpFileName, jobName);
1378 remove(tmpFileName);
1379 #endif /*VMS*/
1380 return;
1384 ** Wrapper for GetExistingFilename which uses the current window's path
1385 ** (if set) as the default directory.
1387 int PromptForExistingFile(WindowInfo *window, char *prompt, char *fullname)
1389 char *savedDefaultDir;
1390 int retVal;
1392 /* Temporarily set default directory to window->path, prompt for file,
1393 then, if the call was unsuccessful, restore the original default
1394 directory */
1395 savedDefaultDir = GetFileDialogDefaultDirectory();
1396 if (*window->path != '\0')
1397 SetFileDialogDefaultDirectory(window->path);
1398 retVal = GetExistingFilename(window->shell, prompt, fullname);
1399 if (retVal != GFN_OK)
1400 SetFileDialogDefaultDirectory(savedDefaultDir);
1401 if (savedDefaultDir != NULL)
1402 XtFree(savedDefaultDir);
1403 return retVal;
1407 ** Wrapper for HandleCustomNewFileSB which uses the current window's path
1408 ** (if set) as the default directory, and asks about embedding newlines
1409 ** to make wrapping permanent.
1411 int PromptForNewFile(WindowInfo *window, char *prompt, char *fullname,
1412 int *fileFormat, int *addWrap)
1414 int n, retVal;
1415 Arg args[20];
1416 XmString s1, s2;
1417 Widget fileSB, wrapToggle;
1418 Widget formatForm, formatBtns, unixFormat, dosFormat, macFormat;
1419 char *savedDefaultDir;
1421 *fileFormat = window->fileFormat;
1423 /* Temporarily set default directory to window->path, prompt for file,
1424 then, if the call was unsuccessful, restore the original default
1425 directory */
1426 savedDefaultDir = GetFileDialogDefaultDirectory();
1427 if (*window->path != '\0')
1428 SetFileDialogDefaultDirectory(window->path);
1430 /* Present a file selection dialog with an added field for requesting
1431 long line wrapping to become permanent via inserted newlines */
1432 n = 0;
1433 XtSetArg(args[n],
1434 XmNselectionLabelString,
1435 s1 = XmStringCreateLocalized("New File Name:")); n++;
1436 XtSetArg(args[n], XmNdialogStyle, XmDIALOG_FULL_APPLICATION_MODAL); n++;
1437 XtSetArg(args[n],
1438 XmNdialogTitle,
1439 s2 = XmStringCreateSimple(prompt)); n++;
1440 fileSB = CreateFileSelectionDialog(window->shell,"FileSelect",args,n);
1441 XmStringFree(s1);
1442 XmStringFree(s2);
1443 formatForm = XtVaCreateManagedWidget("formatForm", xmFormWidgetClass,
1444 fileSB, NULL);
1445 formatBtns = XtVaCreateManagedWidget("formatBtns", xmRowColumnWidgetClass,
1446 formatForm,
1447 XmNradioBehavior, XmONE_OF_MANY,
1448 XmNorientation, XmHORIZONTAL,
1449 XmNpacking, XmPACK_TIGHT,
1450 XmNtopAttachment, XmATTACH_FORM,
1451 XmNleftAttachment, XmATTACH_FORM, NULL);
1452 XtVaCreateManagedWidget("formatBtns", xmLabelWidgetClass, formatBtns,
1453 XmNlabelString, s1=XmStringCreateSimple("Format:"), NULL);
1454 XmStringFree(s1);
1455 unixFormat = XtVaCreateManagedWidget("unixFormat",
1456 xmToggleButtonWidgetClass, formatBtns, XmNlabelString,
1457 s1=XmStringCreateSimple("Unix"),
1458 XmNset, *fileFormat == UNIX_FILE_FORMAT,
1459 XmNuserData, UNIX_FILE_FORMAT,
1460 XmNmarginHeight, 0, XmNalignment, XmALIGNMENT_BEGINNING,
1461 XmNmnemonic, 'U', NULL);
1462 XmStringFree(s1);
1463 XtAddCallback(unixFormat, XmNvalueChangedCallback, setFormatCB,
1464 fileFormat);
1465 dosFormat = XtVaCreateManagedWidget("dosFormat",
1466 xmToggleButtonWidgetClass, formatBtns, XmNlabelString,
1467 s1=XmStringCreateSimple("DOS"),
1468 XmNset, *fileFormat == DOS_FILE_FORMAT,
1469 XmNuserData, DOS_FILE_FORMAT,
1470 XmNmarginHeight, 0, XmNalignment, XmALIGNMENT_BEGINNING,
1471 XmNmnemonic, 'O', NULL);
1472 XmStringFree(s1);
1473 XtAddCallback(dosFormat, XmNvalueChangedCallback, setFormatCB,
1474 fileFormat);
1475 macFormat = XtVaCreateManagedWidget("macFormat",
1476 xmToggleButtonWidgetClass, formatBtns, XmNlabelString,
1477 s1=XmStringCreateSimple("Macintosh"),
1478 XmNset, *fileFormat == MAC_FILE_FORMAT,
1479 XmNuserData, MAC_FILE_FORMAT,
1480 XmNmarginHeight, 0, XmNalignment, XmALIGNMENT_BEGINNING,
1481 XmNmnemonic, 'M', NULL);
1482 XmStringFree(s1);
1483 XtAddCallback(macFormat, XmNvalueChangedCallback, setFormatCB,
1484 fileFormat);
1485 if (window->wrapMode == CONTINUOUS_WRAP) {
1486 wrapToggle = XtVaCreateManagedWidget("addWrap",
1487 xmToggleButtonWidgetClass, formatForm, XmNlabelString,
1488 s1=XmStringCreateSimple("Add line breaks where wrapped"),
1489 XmNalignment, XmALIGNMENT_BEGINNING,
1490 XmNmnemonic, 'A',
1491 XmNtopAttachment, XmATTACH_WIDGET,
1492 XmNtopWidget, formatBtns,
1493 XmNleftAttachment, XmATTACH_FORM, NULL);
1494 XtAddCallback(wrapToggle, XmNvalueChangedCallback, addWrapCB,
1495 addWrap);
1496 XmStringFree(s1);
1498 *addWrap = False;
1499 XtVaSetValues(XmFileSelectionBoxGetChild(fileSB,
1500 XmDIALOG_FILTER_LABEL), XmNmnemonic, 'l', XmNuserData,
1501 XmFileSelectionBoxGetChild(fileSB, XmDIALOG_FILTER_TEXT), NULL);
1502 XtVaSetValues(XmFileSelectionBoxGetChild(fileSB,
1503 XmDIALOG_DIR_LIST_LABEL), XmNmnemonic, 'D', XmNuserData,
1504 XmFileSelectionBoxGetChild(fileSB, XmDIALOG_DIR_LIST), NULL);
1505 XtVaSetValues(XmFileSelectionBoxGetChild(fileSB,
1506 XmDIALOG_LIST_LABEL), XmNmnemonic, 'F', XmNuserData,
1507 XmFileSelectionBoxGetChild(fileSB, XmDIALOG_LIST), NULL);
1508 XtVaSetValues(XmFileSelectionBoxGetChild(fileSB,
1509 XmDIALOG_SELECTION_LABEL), XmNmnemonic,
1510 prompt[strspn(prompt, "lFD")], XmNuserData,
1511 XmFileSelectionBoxGetChild(fileSB, XmDIALOG_TEXT), NULL);
1512 AddDialogMnemonicHandler(fileSB, FALSE);
1513 RemapDeleteKey(XmFileSelectionBoxGetChild(fileSB, XmDIALOG_FILTER_TEXT));
1514 RemapDeleteKey(XmFileSelectionBoxGetChild(fileSB, XmDIALOG_TEXT));
1515 retVal = HandleCustomNewFileSB(fileSB, fullname,
1516 window->filenameSet ? window->filename : NULL);
1518 if (retVal != GFN_OK)
1519 SetFileDialogDefaultDirectory(savedDefaultDir);
1520 if (savedDefaultDir != NULL)
1521 XtFree(savedDefaultDir);
1522 return retVal;
1526 ** Find a name for an untitled file, unique in the name space of in the opened
1527 ** files in this session, i.e. Untitled or Untitled_nn, and write it into
1528 ** the string "name".
1530 void UniqueUntitledName(char *name)
1532 WindowInfo *w;
1533 int i;
1535 for (i=0; i<INT_MAX; i++) {
1536 if (i == 0)
1537 sprintf(name, "Untitled");
1538 else
1539 sprintf(name, "Untitled_%d", i);
1540 for (w=WindowList; w!=NULL; w=w->next)
1541 if (!strcmp(w->filename, name))
1542 break;
1543 if (w == NULL)
1544 break;
1549 ** Check if the file in the window was changed by an external source.
1550 ** and put up a warning dialog if it has.
1552 void CheckForChangesToFile(WindowInfo *window)
1554 static WindowInfo *lastCheckWindow;
1555 static Time lastCheckTime = 0;
1556 char fullname[MAXPATHLEN];
1557 struct stat statbuf;
1558 Time timestamp;
1559 FILE *fp;
1560 int resp, silent = 0;
1561 XWindowAttributes winAttr;
1563 if(!window->filenameSet)
1564 return;
1566 /* If last check was very recent, don't impact performance */
1567 timestamp = XtLastTimestampProcessed(XtDisplay(window->shell));
1568 if (window == lastCheckWindow &&
1569 timestamp - lastCheckTime < MOD_CHECK_INTERVAL)
1570 return;
1571 lastCheckWindow = window;
1572 lastCheckTime = timestamp;
1574 /* Update the status, but don't pop up a dialog if we're called
1575 from a place where the window might be iconic (e.g., from the
1576 replace dialog) or on another desktop.
1578 This works, but I bet it costs a round-trip to the server.
1579 Might be better to capture MapNotify/Unmap events instead.
1581 For tabs that are not on top, we don't want the dialog either,
1582 and we don't even need to contact the server to find out. By
1583 performing this check first, we avoid a server round-trip for
1584 most files in practice. */
1585 if (!IsTopDocument(window))
1586 silent = 1;
1587 else {
1588 XGetWindowAttributes(XtDisplay(window->shell),
1589 XtWindow(window->shell),
1590 &winAttr);
1592 if (winAttr.map_state != IsViewable)
1593 silent = 1;
1596 /* Get the file mode and modification time */
1597 strcpy(fullname, window->path);
1598 strcat(fullname, window->filename);
1599 if (stat(fullname, &statbuf) != 0) {
1600 /* Return if we've already warned the user or we can't warn him now */
1601 if (window->fileMissing || silent)
1602 return;
1603 /* Can't stat the file -- maybe it's been deleted.
1604 The filename is now invalid */
1605 window->fileMissing = TRUE;
1606 window->lastModTime = 1;
1607 /* Warn the user, if they like to be warned (Maybe this should be its
1608 own preference setting: GetPrefWarnFileDeleted() ) */
1609 if (GetPrefWarnFileMods()) {
1610 /* See note below about pop-up timing and XUngrabPointer */
1611 XUngrabPointer(XtDisplay(window->shell), timestamp);
1612 if( errno == EACCES )
1613 resp = 1 + DialogF(DF_ERR, window->shell, 2,
1614 "File not Accessible",
1615 "You no longer have access to file \"%s\".\n"
1616 "Another program may have changed the permissions one of\n"
1617 "its parent directories.\nSave as a new file?",
1618 "Save As...", "Cancel", window->filename);
1619 else
1620 resp = DialogF(DF_ERR, window->shell, 3, "File not found",
1621 "Error while checking the status of file \"%s\":\n"
1622 " \"%s\"\n"
1623 "Another program may have deleted or moved it.\n"
1624 "Re-Save file or Save as a new file?", "Re-Save",
1625 "Save As...", "Cancel", window->filename,
1626 errorString());
1627 if (resp == 1)
1628 SaveWindow(window);
1629 else if (resp == 2)
1630 SaveWindowAs(window, NULL, 0);
1632 /* A missing or (re-)saved file can't be read-only. */
1633 SET_PERM_LOCKED(window->lockReasons, False);
1634 UpdateWindowTitle(window);
1635 UpdateWindowReadOnly(window);
1636 return;
1639 /* Check that the file's read-only status is still correct (but
1640 only if the file can still be opened successfully in read mode) */
1641 if (window->fileMode != statbuf.st_mode) {
1642 window->fileMode = statbuf.st_mode;
1643 if ((fp = fopen(fullname, "r")) != NULL) {
1644 int readOnly;
1645 fclose(fp);
1646 #ifndef DONT_USE_ACCESS
1647 readOnly = access(fullname, W_OK) != 0;
1648 #else
1649 if (((fp = fopen(fullname, "r+")) != NULL)) {
1650 readOnly = FALSE;
1651 fclose(fp);
1652 } else
1653 readOnly = TRUE;
1654 #endif
1655 if (IS_PERM_LOCKED(window->lockReasons) != readOnly) {
1656 SET_PERM_LOCKED(window->lockReasons, readOnly);
1657 UpdateWindowTitle(window);
1658 UpdateWindowReadOnly(window);
1663 /* Warn the user if the file has been modified, unless checking is
1664 turned off or the user has already been warned. Popping up a dialog
1665 from a focus callback (which is how this routine is usually called)
1666 seems to catch Motif off guard, and if the timing is just right, the
1667 dialog can be left with a still active pointer grab from a Motif menu
1668 which is still in the process of popping down. The workaround, below,
1669 of calling XUngrabPointer is inelegant but seems to fix the problem. */
1670 if (!silent &&
1671 ((window->lastModTime != 0 &&
1672 window->lastModTime != statbuf.st_mtime) ||
1673 window->fileMissing) ){
1674 window->lastModTime = 0; /* Inhibit further warnings */
1675 window->fileMissing = FALSE;
1676 if (!GetPrefWarnFileMods())
1677 return;
1678 if (GetPrefWarnRealFileMods() &&
1679 !cmpWinAgainstFile(window, fullname)) {
1680 /* Contents hasn't changed. Update the modification time. */
1681 window->lastModTime = statbuf.st_mtime;
1682 return;
1684 XUngrabPointer(XtDisplay(window->shell), timestamp);
1685 if (window->fileChanged)
1686 resp = DialogF(DF_WARN, window->shell, 2,
1687 "File modified externally",
1688 "%s has been modified by another program. Reload?\n\n"
1689 "WARNING: Reloading will discard changes made in this\n"
1690 "editing session!", "Reload", "Cancel", window->filename);
1691 else
1692 resp = DialogF(DF_WARN, window->shell, 2,
1693 "File modified externally",
1694 "%s has been modified by another\nprogram. Reload?",
1695 "Reload", "Cancel", window->filename);
1696 if (resp == 1)
1697 RevertToSaved(window);
1702 ** Return true if the file displayed in window has been modified externally
1703 ** to nedit. This should return FALSE if the file has been deleted or is
1704 ** unavailable.
1706 static int fileWasModifiedExternally(WindowInfo *window)
1708 char fullname[MAXPATHLEN];
1709 struct stat statbuf;
1711 if(!window->filenameSet)
1712 return FALSE;
1713 /* if (window->lastModTime == 0)
1714 return FALSE; */
1715 strcpy(fullname, window->path);
1716 strcat(fullname, window->filename);
1717 if (stat(fullname, &statbuf) != 0)
1718 return FALSE;
1719 if (window->lastModTime == statbuf.st_mtime)
1720 return FALSE;
1721 if (GetPrefWarnRealFileMods() &&
1722 !cmpWinAgainstFile(window, fullname)) {
1723 return FALSE;
1725 return TRUE;
1729 ** Check the read-only or locked status of the window and beep and return
1730 ** false if the window should not be written in.
1732 int CheckReadOnly(WindowInfo *window)
1734 if (IS_ANY_LOCKED(window->lockReasons)) {
1735 XBell(TheDisplay, 0);
1736 return True;
1738 return False;
1742 ** Wrapper for strerror so all the calls don't have to be ifdef'd for VMS.
1744 static const char *errorString(void)
1746 #ifdef VMS
1747 return strerror(errno, vaxc$errno);
1748 #else
1749 return strerror(errno);
1750 #endif
1753 #ifdef VMS
1755 ** Removing the VMS version number from a file name (if has one).
1757 void removeVersionNumber(char *fileName)
1759 char *versionStart;
1761 versionStart = strrchr(fileName, ';');
1762 if (versionStart != NULL)
1763 *versionStart = '\0';
1765 #endif /*VMS*/
1768 ** Callback procedure for File Format toggle buttons. Format is stored
1769 ** in userData field of widget button
1771 static void setFormatCB(Widget w, XtPointer clientData, XtPointer callData)
1773 if (XmToggleButtonGetState(w))
1774 XtVaGetValues(w, XmNuserData, clientData, NULL);
1778 ** Callback procedure for toggle button requesting newlines to be inserted
1779 ** to emulate continuous wrapping.
1781 static void addWrapCB(Widget w, XtPointer clientData, XtPointer callData)
1783 int resp;
1784 int *addWrap = (int *)clientData;
1786 if (XmToggleButtonGetState(w))
1788 resp = DialogF(DF_WARN, w, 2, "Add Wrap",
1789 "This operation adds permanent line breaks to\n"
1790 "match the automatic wrapping done by the\n"
1791 "Continuous Wrap mode Preferences Option.\n\n"
1792 "*** This Option is Irreversable ***\n\n"
1793 "Once newlines are inserted, continuous wrapping\n"
1794 "will no longer work automatically on these lines", "OK",
1795 "Cancel");
1796 if (resp == 2)
1798 XmToggleButtonSetState(w, False, False);
1799 *addWrap = False;
1800 } else
1802 *addWrap = True;
1804 } else
1806 *addWrap = False;
1811 ** Change a window created in NEdit's continuous wrap mode to the more
1812 ** conventional Unix format of embedded newlines. Indicate to the user
1813 ** by turning off Continuous Wrap mode.
1815 static void addWrapNewlines(WindowInfo *window)
1817 int fileLen, i, insertPositions[MAX_PANES], topLines[MAX_PANES];
1818 int horizOffset;
1819 Widget text;
1820 char *fileString;
1822 /* save the insert and scroll positions of each pane */
1823 for (i=0; i<=window->nPanes; i++) {
1824 text = i==0 ? window->textArea : window->textPanes[i-1];
1825 insertPositions[i] = TextGetCursorPos(text);
1826 TextGetScroll(text, &topLines[i], &horizOffset);
1829 /* Modify the buffer to add wrapping */
1830 fileString = TextGetWrapped(window->textArea, 0,
1831 window->buffer->length, &fileLen);
1832 BufSetAll(window->buffer, fileString);
1833 XtFree(fileString);
1835 /* restore the insert and scroll positions of each pane */
1836 for (i=0; i<=window->nPanes; i++) {
1837 text = i==0 ? window->textArea : window->textPanes[i-1];
1838 TextSetCursorPos(text, insertPositions[i]);
1839 TextSetScroll(text, topLines[i], 0);
1842 /* Show the user that something has happened by turning off
1843 Continuous Wrap mode */
1844 SetToggleButtonState(window, window->continuousWrapItem, False, True);
1848 * Number of bytes read at once by cmpWinAgainstFile
1850 #define PREFERRED_CMPBUF_LEN 32768
1853 * Check if the contens of the textBuffer *buf is equal
1854 * the contens of the file named fileName. The format of
1855 * the file (UNIX/DOS/MAC) is handled properly.
1857 * Return values
1858 * 0: no difference found
1859 * !0: difference found or could not compare contents.
1861 static int cmpWinAgainstFile(WindowInfo *window, const char *fileName)
1863 char fileString[PREFERRED_CMPBUF_LEN + 2];
1864 struct stat statbuf;
1865 int fileLen, restLen, nRead, bufPos, rv, offset, filePos;
1866 char pendingCR = 0;
1867 int fileFormat = window->fileFormat;
1868 char message[MAXPATHLEN+50];
1869 textBuffer *buf = window->buffer;
1870 FILE *fp;
1872 fp = fopen(fileName, "r");
1873 if (!fp)
1874 return (1);
1875 if (fstat(fileno(fp), &statbuf) != 0) {
1876 fclose(fp);
1877 return (1);
1880 fileLen = statbuf.st_size;
1881 /* For DOS files, we can't simply check the length */
1882 if (fileFormat != DOS_FILE_FORMAT) {
1883 if (fileLen != buf->length) {
1884 fclose(fp);
1885 return (1);
1887 } else {
1888 /* If a DOS file is smaller on disk, it's certainly different */
1889 if (fileLen < buf->length) {
1890 fclose(fp);
1891 return (1);
1895 /* For large files, the comparison can take a while. If it takes too long,
1896 the user should be given a clue about what is happening. */
1897 sprintf(message, "Comparing externally modified %s ...", window->filename);
1898 restLen = min(PREFERRED_CMPBUF_LEN, fileLen);
1899 bufPos = 0;
1900 filePos = 0;
1901 while (restLen > 0) {
1902 AllWindowsBusy(message);
1903 if (pendingCR) {
1904 fileString[0] = pendingCR;
1905 offset = 1;
1906 } else {
1907 offset = 0;
1910 nRead = fread(fileString+offset, sizeof(char), restLen, fp);
1911 if (nRead != restLen) {
1912 fclose(fp);
1913 AllWindowsUnbusy();
1914 return (1);
1916 filePos += nRead;
1918 nRead += offset;
1920 if (fileFormat == MAC_FILE_FORMAT)
1921 ConvertFromMacFileString(fileString, nRead);
1922 else if (fileFormat == DOS_FILE_FORMAT)
1923 ConvertFromDosFileString(fileString, &nRead, &pendingCR);
1925 /* Beware of 0 chars ! */
1926 BufSubstituteNullChars(fileString, nRead, buf);
1927 rv = BufCmp(buf, bufPos, nRead, fileString);
1928 if (rv) {
1929 fclose(fp);
1930 AllWindowsUnbusy();
1931 return (rv);
1933 bufPos += nRead;
1934 restLen = min(fileLen - filePos, PREFERRED_CMPBUF_LEN);
1936 AllWindowsUnbusy();
1937 fclose(fp);
1938 if (pendingCR) {
1939 rv = BufCmp(buf, bufPos, 1, &pendingCR);
1940 if (rv) {
1941 return (rv);
1943 bufPos += 1;
1945 if (bufPos != buf->length) {
1946 return (1);
1948 return (0);
1951 static int min(int i1, int i2)
1953 return i1 <= i2 ? i1 : i2;