Revive upgrade message to 5.4 file format
[nedit.git] / source / file.c
blob82f9f6ddcb330a8834139c8c147ce54f2980b6d2
1 static const char CVSID[] = "$Id: file.c,v 1.51 2002/08/27 05:39:27 n8gray 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. *
12 * *
13 * This software is distributed in the hope that it will be useful, but WITHOUT *
14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License *
16 * for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License along with *
19 * software; if not, write to the Free Software Foundation, Inc., 59 Temple *
20 * Place, Suite 330, Boston, MA 02111-1307 USA *
21 * *
22 * Nirvana Text Editor *
23 * May 10, 1991 *
24 * *
25 * Written by Mark Edel *
26 * *
27 *******************************************************************************/
29 #ifdef HAVE_CONFIG_H
30 #include "../config.h"
31 #endif
33 #include "file.h"
34 #include "textBuf.h"
35 #include "text.h"
36 #include "window.h"
37 #include "preferences.h"
38 #include "undo.h"
39 #include "menu.h"
40 #include "tags.h"
41 #include "../util/misc.h"
42 #include "../util/DialogF.h"
43 #include "../util/fileUtils.h"
44 #include "../util/getfiles.h"
45 #include "../util/printUtils.h"
46 #include "../util/utils.h"
48 #include <stdio.h>
49 #include <errno.h>
50 #include <limits.h>
51 #include <string.h>
52 #include <stdlib.h>
53 #ifdef VMS
54 #include "../util/VMSparam.h"
55 #include <types.h>
56 #include <stat.h>
57 #include <unixio.h>
58 #else
59 #include <sys/types.h>
60 #include <sys/stat.h>
61 #ifndef __MVS__
62 #include <sys/param.h>
63 #endif
64 #include <unistd.h>
65 #include <fcntl.h>
66 #endif /*VMS*/
68 #include <Xm/Xm.h>
69 #include <Xm/ToggleB.h>
70 #include <Xm/FileSB.h>
71 #include <Xm/RowColumn.h>
72 #include <Xm/Form.h>
73 #include <Xm/Label.h>
75 #ifdef HAVE_DEBUG_H
76 #include "../debug.h"
77 #endif
79 /* Parameters to algorithm used to auto-detect DOS format files. NEdit will
80 scan up to the lesser of FORMAT_SAMPLE_LINES lines and FORMAT_SAMPLE_CHARS
81 characters of the beginning of the file, checking that all newlines are
82 paired with carriage returns. If even a single counterexample exists,
83 the file is judged to be in Unix format. */
84 #define FORMAT_SAMPLE_LINES 5
85 #define FORMAT_SAMPLE_CHARS 2000
87 /* Maximum frequency in miliseconds of checking for external modifications.
88 The periodic check is only performed on buffer modification, and the check
89 interval is only to prevent checking on every keystroke in case of a file
90 system which is slow to process stat requests (which I'm not sure exists) */
91 #define MOD_CHECK_INTERVAL 3000
93 static int doSave(WindowInfo *window);
94 static void safeClose(WindowInfo *window);
95 static int doOpen(WindowInfo *window, const char *name, const char *path,
96 int flags);
97 static void backupFileName(WindowInfo *window, char *name, int len);
98 static int writeBckVersion(WindowInfo *window);
99 static int bckError(WindowInfo *window, const char *errString, const char *file);
100 static int fileWasModifiedExternally(WindowInfo *window);
101 static char *errorString(void);
102 static void addWrapNewlines(WindowInfo *window);
103 static void setFormatCB(Widget w, XtPointer clientData, XtPointer callData);
104 static void addWrapCB(Widget w, XtPointer clientData, XtPointer callData);
105 static int formatOfFile(const char *fileString);
106 static void convertFromDosFileString(char *inString, int *length);
107 static void convertFromMacFileString(char *fileString, int length);
108 static int convertToDosFileString(char **fileString, int *length);
109 static void convertToMacFileString(char *fileString, int length);
110 #ifdef VMS
111 void removeVersionNumber(char *fileName);
112 #endif /*VMS*/
114 void EditNewFile(char *geometry, int iconic, const char *languageMode,
115 const char *defaultPath)
117 char name[MAXPATHLEN];
118 WindowInfo *window;
120 /*... test for creatability? */
122 /* Find a (relatively) unique name for the new file */
123 UniqueUntitledName(name);
125 /* create the window */
126 window = CreateWindow(name, geometry, iconic);
127 strcpy(window->filename, name);
128 strcpy(window->path, defaultPath ? defaultPath : "");
129 window->filenameSet = FALSE;
130 window->fileFormat = UNIX_FILE_FORMAT;
131 window->lastModTime = 0;
132 SetWindowModified(window, FALSE);
133 CLEAR_ALL_LOCKS(window->lockReasons);
134 UpdateWindowReadOnly(window);
135 UpdateStatsLine(window);
136 UpdateWindowTitle(window);
137 if (languageMode == NULL)
138 DetermineLanguageMode(window, True);
139 else
140 SetLanguageMode(window, FindLanguageMode(languageMode), True);
144 ** Open an existing file specified by name and path. Use the window inWindow
145 ** unless inWindow is NULL or points to a window which is already in use
146 ** (displays a file other than Untitled, or is Untitled but modified). Flags
147 ** can be any of:
149 ** CREATE: If file is not found, (optionally) prompt the
150 ** user whether to create
151 ** SUPPRESS_CREATE_WARN When creating a file, don't ask the user
152 ** PREF_READ_ONLY Make the file read-only regardless
154 ** If languageMode is passed as NULL, it will be determined automatically
155 ** from the file extension or file contents.
157 WindowInfo *EditExistingFile(WindowInfo *inWindow, const char *name,
158 const char *path, int flags, char *geometry, int iconic,
159 const char *languageMode)
161 WindowInfo *window;
162 char fullname[MAXPATHLEN];
164 /* first look to see if file is already displayed in a window */
165 window = FindWindowWithFile(name, path);
166 if (window != NULL) {
167 RaiseShellWindow(window->shell);
168 return window;
171 /* If an existing window isn't specified; or the window is already
172 in use (not Untitled or Untitled and modified), or is currently
173 busy running a macro; create the window */
174 if (inWindow == NULL || inWindow->filenameSet || inWindow->fileChanged ||
175 inWindow->macroCmdData != NULL)
176 window = CreateWindow(name, geometry, iconic);
177 else {
178 window = inWindow;
179 RaiseShellWindow(window->shell);
182 /* Open the file */
183 if (!doOpen(window, name, path, flags)) {
184 /* The user may have destroyed the window instead of closing the
185 warning dialog; don't close it twice */
186 safeClose(window);
187 return NULL;
190 /* Bring the title bar and statistics line up to date, doOpen does
191 not necessarily set the window title or read-only status */
192 UpdateWindowTitle(window);
193 UpdateWindowReadOnly(window);
194 UpdateStatsLine(window);
196 /* Add the name to the convenience menu of previously opened files */
197 strcpy(fullname, path);
198 strcat(fullname, name);
199 if(GetPrefAlwaysCheckRelTagsSpecs())
200 AddRelTagsFile(GetPrefTagFile(), path, TAG);
201 AddToPrevOpenMenu(fullname);
203 /* Decide what language mode to use, trigger language specific actions */
204 if (languageMode == NULL)
205 DetermineLanguageMode(window, True);
206 else
207 SetLanguageMode(window, FindLanguageMode(languageMode), True);
208 return window;
211 void RevertToSaved(WindowInfo *window)
213 char name[MAXPATHLEN], path[MAXPATHLEN];
214 int i;
215 int insertPositions[MAX_PANES], topLines[MAX_PANES];
216 int horizOffsets[MAX_PANES];
217 int openFlags = 0;
218 Widget text;
220 /* Can't revert untitled windows */
221 if (!window->filenameSet) {
222 DialogF(DF_WARN, window->shell, 1,
223 "Window was never saved, can't re-read", "Dismiss");
224 return;
227 /* save insert & scroll positions of all of the panes to restore later */
228 for (i=0; i<=window->nPanes; i++) {
229 text = i==0 ? window->textArea : window->textPanes[i-1];
230 insertPositions[i] = TextGetCursorPos(text);
231 TextGetScroll(text, &topLines[i], &horizOffsets[i]);
234 /* re-read the file, update the window title if new file is different */
235 strcpy(name, window->filename);
236 strcpy(path, window->path);
237 RemoveBackupFile(window);
238 ClearUndoList(window);
239 openFlags |= IS_USER_LOCKED(window->lockReasons) ? PREF_READ_ONLY : 0;
240 if (!doOpen(window, name, path, openFlags)) {
241 /* The user may have destroyed the window instead of closing the
242 warning dialog; don't close it twice */
243 safeClose(window);
244 return;
246 UpdateWindowTitle(window);
247 UpdateWindowReadOnly(window);
249 /* restore the insert and scroll positions of each pane */
250 for (i=0; i<=window->nPanes; i++) {
251 text = i==0 ? window->textArea : window->textPanes[i-1];
252 TextSetCursorPos(text, insertPositions[i]);
253 TextSetScroll(text, topLines[i], horizOffsets[i]);
258 ** Checks whether a window is still alive, and closes it only if so.
259 ** Intended to be used when the file could not be opened for some reason.
260 ** Normally the window is still alive, but the user may have closed the
261 ** window instead of the error dialog. In that case, we shouldn't close the
262 ** window a second time.
264 static void safeClose(WindowInfo *window)
266 WindowInfo* p = WindowList;
267 while(p) {
268 if (p == window) {
269 CloseWindow(window);
270 return;
272 p = p->next;
276 static int doOpen(WindowInfo *window, const char *name, const char *path,
277 int flags)
279 char fullname[MAXPATHLEN];
280 struct stat statbuf;
281 int fileLen, readLen;
282 char *fileString, *c;
283 FILE *fp = NULL;
284 int fd;
285 int resp;
287 /* initialize lock reasons */
288 CLEAR_ALL_LOCKS(window->lockReasons);
290 /* Update the window data structure */
291 strcpy(window->filename, name);
292 strcpy(window->path, path);
293 window->filenameSet = TRUE;
295 /* Get the full name of the file */
296 strcpy(fullname, path);
297 strcat(fullname, name);
299 /* Open the file */
300 #ifdef USE_ACCESS /* The only advantage of this is if you use clearcase,
301 which messes up the mtime of files opened with r+,
302 even if they're never actually written. */
304 if ((fp = fopen(fullname, "r")) != NULL) {
305 if(access(fullname, W_OK) != 0)
306 SET_PERM_LOCKED(window->lockReasons, TRUE);
307 #else
308 fp = fopen(fullname, "rb+");
309 if (fp == NULL) {
310 /* Error opening file or file is not writeable */
311 fp = fopen(fullname, "rb");
312 if (fp != NULL) {
313 /* File is read only */
314 SET_PERM_LOCKED(window->lockReasons, TRUE);
315 #endif
316 } else if (flags & CREATE && errno == ENOENT) {
317 /* Give option to create (or to exit if this is the only window) */
318 if (!(flags & SUPPRESS_CREATE_WARN)) {
319 if (WindowList == window && window->next == NULL)
320 resp = DialogF(DF_WARN, window->shell, 3,
321 "Can't open %s:\n%s", "New File", "Cancel",
322 "Exit NEdit", fullname, errorString());
323 else
324 resp = DialogF(DF_WARN, window->shell, 2,
325 "Can't open %s:\n%s", "New File", "Cancel",
326 fullname, errorString());
327 if (resp == 2)
328 return FALSE;
329 else if (resp == 3)
330 exit(EXIT_SUCCESS);
332 /* Test if new file can be created */
333 if ((fd = creat(fullname, 0666)) == -1) {
334 DialogF(DF_ERR, window->shell, 1, "Can't create %s:\n%s",
335 "Dismiss", fullname, errorString());
336 return FALSE;
337 } else {
338 #ifdef VMS
339 /* get correct version number and close before removing */
340 getname(fd, fullname);
341 #endif
342 close(fd);
343 remove(fullname);
345 SetWindowModified(window, FALSE);
346 if ((flags & PREF_READ_ONLY) != 0) {
347 SET_USER_LOCKED(window->lockReasons, TRUE);
349 UpdateWindowReadOnly(window);
350 return TRUE;
351 } else {
352 /* A true error */
353 DialogF(DF_ERR, window->shell, 1, "Could not open %s%s:\n%s",
354 "Dismiss", path, name, errorString());
355 return FALSE;
359 /* Get the length of the file, the protection mode, and the time of the
360 last modification to the file */
361 if (fstat(fileno(fp), &statbuf) != 0) {
362 fclose(fp);
363 DialogF(DF_ERR, window->shell, 1, "Error opening %s", "Dismiss", name);
364 return FALSE;
366 if (S_ISDIR(statbuf.st_mode)) {
367 fclose(fp);
368 DialogF(DF_ERR, window->shell, 1, "Can't open directory %s", "Dismiss", name);
369 return FALSE;
371 #ifdef S_ISBLK
372 if (S_ISBLK(statbuf.st_mode)) {
373 fclose(fp);
374 DialogF(DF_ERR, window->shell, 1, "Can't open block device %s", "Dismiss", name);
375 return FALSE;
377 #endif
378 fileLen = statbuf.st_size;
379 window->fileMode = statbuf.st_mode;
380 window->lastModTime = statbuf.st_mtime;
382 /* Allocate space for the whole contents of the file (unfortunately) */
383 fileString = (char *)malloc(fileLen+1); /* +1 = space for null */
384 if (fileString == NULL) {
385 fclose(fp);
386 DialogF(DF_ERR, window->shell, 1, "File is too large to edit",
387 "Dismiss");
388 return FALSE;
391 /* Read the file into fileString and terminate with a null */
392 readLen = fread(fileString, sizeof(char), fileLen, fp);
393 if (ferror(fp)) {
394 fclose(fp);
395 DialogF(DF_ERR, window->shell, 1, "Error reading %s:\n%s", "Dismiss",
396 name, errorString());
397 free(fileString);
398 return FALSE;
400 fileString[readLen] = 0;
402 /* Close the file */
403 if (fclose(fp) != 0) {
404 /* unlikely error */
405 DialogF(DF_WARN, window->shell, 1, "Unable to close file", "Dismiss");
406 /* we read it successfully, so continue */
409 /* Detect and convert DOS and Macintosh format files */
410 window->fileFormat = formatOfFile(fileString);
411 if (window->fileFormat == DOS_FILE_FORMAT)
412 convertFromDosFileString(fileString, &readLen);
413 else if (window->fileFormat == MAC_FILE_FORMAT)
414 convertFromMacFileString(fileString, readLen);
416 /* Display the file contents in the text widget */
417 window->ignoreModify = True;
418 BufSetAll(window->buffer, fileString);
419 window->ignoreModify = False;
421 /* Check that the length that the buffer thinks it has is the same
422 as what we gave it. If not, there were probably nuls in the file.
423 Substitute them with another character. If that is impossible, warn
424 the user, make the file read-only, and force a substitution */
425 if (window->buffer->length != readLen) {
426 if (!BufSubstituteNullChars(fileString, readLen, window->buffer)) {
427 resp = DialogF(DF_ERR, window->shell, 2,
428 "Too much binary data in file. You may view\n\
429 it, but not modify or re-save its contents.", "View", "Cancel");
430 if (resp == 2)
431 return FALSE;
432 SET_TMBD_LOCKED(window->lockReasons, TRUE);
433 for (c=fileString; c<&fileString[readLen]; c++)
434 if (*c == '\0')
435 *c = 0xfe;
436 window->buffer->nullSubsChar = 0xfe;
438 window->ignoreModify = True;
439 BufSetAll(window->buffer, fileString);
440 window->ignoreModify = False;
443 /* Release the memory that holds fileString */
444 free(fileString);
446 /* Set window title and file changed flag */
447 if ((flags & PREF_READ_ONLY) != 0) {
448 SET_USER_LOCKED(window->lockReasons, TRUE);
450 if (IS_PERM_LOCKED(window->lockReasons)) {
451 window->fileChanged = FALSE;
452 UpdateWindowTitle(window);
453 } else {
454 SetWindowModified(window, FALSE);
455 if (IS_ANY_LOCKED(window->lockReasons)) {
456 UpdateWindowTitle(window);
459 UpdateWindowReadOnly(window);
461 return TRUE;
464 int IncludeFile(WindowInfo *window, const char *name)
466 struct stat statbuf;
467 int fileLen, readLen;
468 char *fileString;
469 FILE *fp = NULL;
471 /* Open the file */
472 fp = fopen(name, "rb");
473 if (fp == NULL) {
474 DialogF(DF_ERR, window->shell, 1, "Could not open %s:\n%s",
475 "Dismiss", name, errorString());
476 return FALSE;
479 /* Get the length of the file */
480 if (fstat(fileno(fp), &statbuf) != 0) {
481 DialogF(DF_ERR, window->shell, 1, "Error opening %s", "Dismiss", name);
482 return FALSE;
484 if (S_ISDIR(statbuf.st_mode)) {
485 DialogF(DF_ERR, window->shell, 1, "Can't open directory %s", "Dismiss", name);
486 return FALSE;
488 fileLen = statbuf.st_size;
490 /* allocate space for the whole contents of the file */
491 fileString = (char *)malloc(fileLen+1); /* +1 = space for null */
492 if (fileString == NULL) {
493 DialogF(DF_ERR, window->shell, 1, "File is too large to include",
494 "Dismiss");
495 return FALSE;
498 /* read the file into fileString and terminate with a null */
499 readLen = fread(fileString, sizeof(char), fileLen, fp);
500 if (ferror(fp)) {
501 DialogF(DF_ERR, window->shell, 1, "Error reading %s:\n%s", "Dismiss",
502 name, errorString());
503 fclose(fp);
504 free(fileString);
505 return FALSE;
507 fileString[readLen] = 0;
509 /* Detect and convert DOS and Macintosh format files */
510 switch (formatOfFile(fileString)) {
511 case DOS_FILE_FORMAT:
512 convertFromDosFileString(fileString, &readLen); break;
513 case MAC_FILE_FORMAT:
514 convertFromMacFileString(fileString, readLen); break;
517 /* If the file contained ascii nulls, re-map them */
518 if (!BufSubstituteNullChars(fileString, readLen, window->buffer))
519 DialogF(DF_ERR, window->shell, 1, "Too much binary data in file",
520 "Dismiss");
522 /* close the file */
523 if (fclose(fp) != 0) {
524 /* unlikely error */
525 DialogF(DF_WARN, window->shell, 1, "Unable to close file", "Dismiss");
526 /* we read it successfully, so continue */
529 /* insert the contents of the file in the selection or at the insert
530 position in the window if no selection exists */
531 if (window->buffer->primary.selected)
532 BufReplaceSelected(window->buffer, fileString);
533 else
534 BufInsert(window->buffer, TextGetCursorPos(window->lastFocus),
535 fileString);
537 /* release the memory that holds fileString */
538 free(fileString);
540 return TRUE;
544 ** Close all files and windows, leaving one untitled window
546 int CloseAllFilesAndWindows(void)
548 while (WindowList->next != NULL ||
549 WindowList->filenameSet || WindowList->fileChanged) {
550 if (!CloseFileAndWindow(WindowList, PROMPT_SBC_DIALOG_RESPONSE))
551 return FALSE;
553 return TRUE;
556 int CloseFileAndWindow(WindowInfo *window, int preResponse)
558 int response, stat;
560 /* Make sure that the window is not in iconified state */
561 RaiseShellWindow(window->shell);
563 /* if window is modified, ask about saving, otherwise just close */
564 if (!window->fileChanged) {
565 CloseWindow(window);
566 /* up-to-date windows don't have outstanding backup files to close */
567 } else {
568 if (preResponse == PROMPT_SBC_DIALOG_RESPONSE) {
569 response = DialogF(DF_WARN, window->shell, 3,
570 "Save %s before closing?", "Yes", "No", "Cancel",
571 window->filename);
573 else {
574 response = preResponse;
576 if (response == YES_SBC_DIALOG_RESPONSE) {
577 /* Save */
578 stat = SaveWindow(window);
579 if (stat)
580 CloseWindow(window);
581 else
582 return FALSE;
583 } else if (response == NO_SBC_DIALOG_RESPONSE) {
584 /* Don't Save */
585 RemoveBackupFile(window);
586 CloseWindow(window);
587 } else /* 3 == Cancel */
588 return FALSE;
590 return TRUE;
593 int SaveWindow(WindowInfo *window)
595 int stat;
597 if (!window->fileChanged || IS_ANY_LOCKED_IGNORING_PERM(window->lockReasons))
598 return TRUE;
599 if (!window->filenameSet)
600 return SaveWindowAs(window, NULL, False);
602 /* Check for external modifications and warn the user */
603 if (GetPrefWarnFileMods() && fileWasModifiedExternally(window)) {
604 if (DialogF(DF_WARN, window->shell, 2,
605 "%s has been modified by another program.\n\n"
606 "Continuing this operation will overwrite any external\n"
607 "modifications to the file since it was opened in NEdit,\n"
608 "and your work or someone else's may potentially be lost.\n\n"
609 "To preserve the modified file, cancel this operation and\n"
610 "use Save As... to save this file under a different name,\n"
611 "or Revert to Saved to revert to the modified version.",
612 "Continue", "Cancel", window->filename) != 1) {
613 window->lastModTime = 0; /* Don't warn again */
614 return FALSE;
618 #ifdef VMS
619 RemoveBackupFile(window);
620 stat = doSave(window);
621 #else
622 if (writeBckVersion(window))
623 return FALSE;
624 stat = doSave(window);
625 RemoveBackupFile(window);
626 #endif /*VMS*/
627 return stat;
630 int SaveWindowAs(WindowInfo *window, const char *newName, int addWrap)
632 int response, retVal, fileFormat;
633 char fullname[MAXPATHLEN], filename[MAXPATHLEN], pathname[MAXPATHLEN];
634 WindowInfo *otherWindow;
636 /* Get the new name for the file */
637 if (newName == NULL) {
638 response = PromptForNewFile(window, "Save File As", fullname,
639 &fileFormat, &addWrap);
640 if (response != GFN_OK)
641 return FALSE;
642 window->fileFormat = fileFormat;
643 } else
644 strcpy(fullname, newName);
646 /* Add newlines if requested */
647 if (addWrap)
648 addWrapNewlines(window);
650 /* If the requested file is this file, just save it and return */
651 if (ParseFilename(fullname, filename, pathname) != 0) {
652 return FALSE;
654 if (!strcmp(window->filename, filename) &&
655 !strcmp(window->path, pathname)) {
656 if (writeBckVersion(window))
657 return FALSE;
658 return doSave(window);
661 /* If the file is open in another window, make user close it. Note that
662 it is possible for user to close the window by hand while the dialog
663 is still up, because the dialog is not application modal, so after
664 doing the dialog, check again whether the window still exists. */
665 otherWindow = FindWindowWithFile(filename, pathname);
666 if (otherWindow != NULL) {
667 response = DialogF(DF_WARN, window->shell, 2,
668 "%s is open in another NEdit window", "Cancel",
669 "Close Other Window", filename);
670 if (response == 1)
671 return FALSE;
672 if (otherWindow == FindWindowWithFile(filename, pathname))
673 if (!CloseFileAndWindow(otherWindow, PROMPT_SBC_DIALOG_RESPONSE))
674 return FALSE;
677 /* Change the name of the file and save it under the new name */
678 RemoveBackupFile(window);
679 strcpy(window->filename, filename);
680 strcpy(window->path, pathname);
681 window->filenameSet = TRUE;
682 window->fileMode = 0;
683 CLEAR_ALL_LOCKS(window->lockReasons);
684 retVal = doSave(window);
685 UpdateWindowTitle(window);
686 UpdateWindowReadOnly(window);
688 /* Add the name to the convenience menu of previously opened files */
689 AddToPrevOpenMenu(fullname);
691 /* If name has changed, language mode may have changed as well */
692 DetermineLanguageMode(window, False);
694 /* Update the stats line with the new filename */
695 UpdateStatsLine(window);
697 return retVal;
700 static int doSave(WindowInfo *window)
702 char *fileString = NULL;
703 char fullname[MAXPATHLEN];
704 struct stat statbuf;
705 FILE *fp;
706 int fileLen;
708 /* Get the full name of the file */
709 strcpy(fullname, window->path);
710 strcat(fullname, window->filename);
712 #ifdef VMS
713 /* strip the version number from the file so VMS will begin a new one */
714 removeVersionNumber(fullname);
715 #endif
717 /* open the file */
718 #ifdef VMS
719 fp = fopen(fullname, "w", "rfm = stmlf");
720 #else
721 fp = fopen(fullname, "wb");
722 #endif /* VMS */
723 if (fp == NULL) {
724 DialogF(DF_WARN, window->shell, 1, "Unable to save %s:\n%s", "Dismiss",
725 window->filename, errorString());
726 return FALSE;
729 #ifdef VMS
730 /* get the complete name of the file including the new version number */
731 fgetname(fp, fullname);
732 #endif
734 /* get the text buffer contents and its length */
735 fileString = BufGetAll(window->buffer);
736 fileLen = window->buffer->length;
738 /* If null characters are substituted for, put them back */
739 BufUnsubstituteNullChars(fileString, window->buffer);
741 /* If the file is to be saved in DOS or Macintosh format, reconvert */
742 if (window->fileFormat == DOS_FILE_FORMAT) {
743 if (!convertToDosFileString(&fileString, &fileLen)) {
744 DialogF(DF_ERR, window->shell, 1, "Out of memory! Try\n"
745 "saving in Unix format", "Dismiss");
746 return FALSE;
748 } else if (window->fileFormat == MAC_FILE_FORMAT)
749 convertToMacFileString(fileString, fileLen);
751 /* add a terminating newline if the file doesn't already have one for
752 Unix utilities which get confused otherwise */
753 if (window->fileFormat == UNIX_FILE_FORMAT && fileLen != 0
754 && fileString[fileLen-1] != '\n'
755 && GetPrefAppendLF())
757 fileString[fileLen++] = '\n'; /* null terminator no longer needed */
760 /* write to the file */
761 #ifdef IBM_FWRITE_BUG
762 write(fileno(fp), fileString, fileLen);
763 #else
764 fwrite(fileString, sizeof(char), fileLen, fp);
765 #endif
766 if (ferror(fp)) {
767 DialogF(DF_ERR, window->shell, 1, "%s not saved:\n%s", "Dismiss",
768 window->filename, errorString());
769 fclose(fp);
770 remove(fullname);
771 XtFree(fileString);
772 return FALSE;
775 /* close the file */
776 if (fclose(fp) != 0) {
777 DialogF(DF_ERR, window->shell,1,"Error closing file:\n%s", "Dismiss",
778 errorString());
779 XtFree(fileString);
780 return FALSE;
783 /* free the text buffer copy returned from XmTextGetString */
784 XtFree(fileString);
786 #ifdef VMS
787 /* reflect the fact that NEdit is now editing a new version of the file */
788 ParseFilename(fullname, window->filename, window->path);
789 #endif /*VMS*/
791 /* success, file was written */
792 SetWindowModified(window, FALSE);
794 /* update the modification time */
795 if (stat(fullname, &statbuf) == 0)
796 window->lastModTime = statbuf.st_mtime;
797 else
798 /* This needs to produce an error message -- the file can't be
799 accessed! */
800 window->lastModTime = 0;
802 return TRUE;
806 ** Create a backup file for the current window. The name for the backup file
807 ** is generated using the name and path stored in the window and adding a
808 ** tilde (~) on UNIX and underscore (_) on VMS to the beginning of the name.
810 int WriteBackupFile(WindowInfo *window)
812 char *fileString = NULL;
813 char name[MAXPATHLEN];
814 FILE *fp;
815 int fd, fileLen;
817 /* Generate a name for the autoSave file */
818 backupFileName(window, name, sizeof(name));
820 /* remove the old backup file.
821 Well, this might fail - we'll notice later however. */
822 remove(name);
824 /* open the file, set more restrictive permissions (using default
825 permissions was somewhat of a security hole, because permissions were
826 independent of those of the original file being edited */
827 #ifdef VMS
828 if ((fp = fopen(name, "w", "rfm = stmlf")) == NULL) {
829 #else
830 if ((fd = open(name, O_CREAT|O_EXCL|O_WRONLY, S_IRUSR | S_IWUSR)) < 0
831 || (fp = fdopen(fd, "w")) == NULL) {
832 #endif /* VMS */
833 DialogF(DF_WARN, window->shell, 1,
834 "Unable to save backup for %s:\n%s\nAutomatic backup is now off",
835 "Dismiss", window->filename, errorString());
836 window->autoSave = FALSE;
837 XmToggleButtonSetState(window->autoSaveItem, FALSE, FALSE);
838 return FALSE;
841 /* Set VMS permissions */
842 #ifdef VMS
843 chmod(name, S_IRUSR | S_IWUSR);
844 #endif
846 /* get the text buffer contents and its length */
847 fileString = BufGetAll(window->buffer);
848 fileLen = window->buffer->length;
850 /* If null characters are substituted for, put them back */
851 BufUnsubstituteNullChars(fileString, window->buffer);
853 /* add a terminating newline if the file doesn't already have one */
854 if (fileLen != 0 && fileString[fileLen-1] != '\n')
855 fileString[fileLen++] = '\n'; /* null terminator no longer needed */
857 /* write out the file */
858 #ifdef IBM_FWRITE_BUG
859 write(fileno(fp), fileString, fileLen);
860 #else
861 fwrite(fileString, sizeof(char), fileLen, fp);
862 #endif
863 if (ferror(fp)) {
864 DialogF(DF_ERR, window->shell, 1,
865 "Error while saving backup for %s:\n%s\nAutomatic backup is now off",
866 "Dismiss", window->filename, errorString());
867 fclose(fp);
868 remove(name);
869 XtFree(fileString);
870 window->autoSave = FALSE;
871 return FALSE;
874 /* close the backup file */
875 if (fclose(fp) != 0) {
876 XtFree(fileString);
877 return FALSE;
880 /* Free the text buffer copy returned from XmTextGetString */
881 XtFree(fileString);
883 return TRUE;
887 ** Remove the backup file associated with this window
889 void RemoveBackupFile(WindowInfo *window)
891 char name[MAXPATHLEN];
893 backupFileName(window, name, sizeof(name));
894 remove(name);
898 ** Generate the name of the backup file for this window from the filename
899 ** and path in the window data structure & write into name
901 static void backupFileName(WindowInfo *window, char *name, int len)
903 char bckname[MAXPATHLEN];
904 #ifdef VMS
905 if (window->filenameSet)
906 sprintf(name, "%s_%s", window->path, window->filename);
907 else
908 sprintf(name, "%s_%s", "SYS$LOGIN:", window->filename);
909 #else
910 strcpy(bckname, "~");
911 strcat(bckname, window->filename);
912 if (window->filenameSet)
913 sprintf(name, "%s~%s", window->path, window->filename);
914 else
915 PrependHome(bckname, name, len);
916 #endif /*VMS*/
920 ** If saveOldVersion is on, copies the existing version of the file to
921 ** <filename>.bck in anticipation of a new version being saved. Returns
922 ** True if backup fails and user requests that the new file not be written.
924 static int writeBckVersion(WindowInfo *window)
926 #ifndef VMS
927 char fullname[MAXPATHLEN], bckname[MAXPATHLEN];
928 struct stat statbuf;
929 FILE *inFP, *outFP;
930 int fd, fileLen;
931 char *fileString;
933 /* Do only if version backups are turned on */
934 if (!window->saveOldVersion) {
935 return False;
938 /* Get the full name of the file */
939 strcpy(fullname, window->path);
940 strcat(fullname, window->filename);
942 /* Generate name for old version */ {
943 if ((int)(strlen(fullname) + 5) > (int)MAXPATHLEN)
944 return bckError(window, "file name too long", window->filename);
946 sprintf(bckname, "%s.bck", fullname);
948 /* Delete the old backup file */
949 /* Errors are ignored; we'll notice them later. */
950 unlink(bckname);
952 /* open the file being edited. If there are problems with the
953 old file, don't bother the user, just skip the backup */
954 inFP = fopen(fullname, "rb");
955 if (inFP == NULL) {
956 return FALSE;
959 /* find the length of the file */
960 if (fstat(fileno(inFP), &statbuf) != 0) {
961 return FALSE;
963 fileLen = statbuf.st_size;
965 /* open the file exclusive and with restrictive permissions. */
966 #ifdef VMS
967 if ((outFP = fopen(bckname, "w", "rfm = stmlf")) == NULL) {
968 #else
969 if ((fd = open(bckname, O_CREAT|O_EXCL|O_WRONLY, S_IRUSR | S_IWUSR)) < 0
970 || (outFP = fdopen(fd, "wb")) == NULL) {
971 #endif /* VMS */
972 fclose(inFP);
973 return bckError(window, "Error open backup file", bckname);
975 #ifdef VMS
976 chmod(bckname, S_IRUSR | S_IWUSR);
977 #endif
979 /* Allocate space for the whole contents of the file */
980 fileString = (char *)malloc(fileLen);
981 if (fileString == NULL) {
982 fclose(inFP);
983 fclose(outFP);
984 return bckError(window, "out of memory", bckname);
987 /* read the file into fileString */
988 fread(fileString, sizeof(char), fileLen, inFP);
989 if (ferror(inFP)) {
990 fclose(inFP);
991 fclose(outFP);
992 free(fileString);
993 return FALSE;
996 /* close the input file, ignore any errors */
997 fclose(inFP);
999 /* write to the file */
1000 #ifdef IBM_FWRITE_BUG
1001 write(fileno(outFP), fileString, fileLen);
1002 #else
1003 fwrite(fileString, sizeof(char), fileLen, outFP);
1004 #endif
1005 if (ferror(outFP)) {
1006 fclose(outFP);
1007 remove(bckname);
1008 XtFree(fileString);
1009 return bckError(window, errorString(), bckname);
1011 XtFree(fileString);
1013 /* close the file */
1014 if (fclose(outFP) != 0)
1015 return bckError(window, errorString(), bckname);
1016 #endif /* VMS */
1018 return FALSE;
1022 ** Error processing for writeBckVersion, gives the user option to cancel
1023 ** the subsequent save, or continue and optionally turn off versioning
1025 static int bckError(WindowInfo *window, const char *errString, const char *file)
1027 int resp;
1029 resp = DialogF(DF_ERR, window->shell, 3,
1030 "Couldn't write .bck (last version) file.\n%s: %s",
1031 "Cancel Save", "Turn off Backups", "Continue", file, errString);
1032 if (resp == 1)
1033 return TRUE;
1034 if (resp == 2) {
1035 window->saveOldVersion = FALSE;
1036 XmToggleButtonSetState(window->saveLastItem, FALSE, FALSE);
1038 return FALSE;
1041 void PrintWindow(WindowInfo *window, int selectedOnly)
1043 textBuffer *buf = window->buffer;
1044 selection *sel = &buf->primary;
1045 char *fileString = NULL;
1046 int fileLen;
1048 /* get the contents of the text buffer from the text area widget. Add
1049 wrapping newlines if necessary to make it match the displayed text */
1050 if (selectedOnly) {
1051 if (!sel->selected) {
1052 XBell(TheDisplay, 0);
1053 return;
1055 if (sel->rectangular) {
1056 fileString = BufGetSelectionText(buf);
1057 fileLen = strlen(fileString);
1058 } else
1059 fileString = TextGetWrapped(window->textArea, sel->start, sel->end,
1060 &fileLen);
1061 } else
1062 fileString = TextGetWrapped(window->textArea, 0, buf->length, &fileLen);
1064 /* If null characters are substituted for, put them back */
1065 BufUnsubstituteNullChars(fileString, buf);
1067 /* add a terminating newline if the file doesn't already have one */
1068 if (fileLen != 0 && fileString[fileLen-1] != '\n')
1069 fileString[fileLen++] = '\n'; /* null terminator no longer needed */
1071 /* Print the string */
1072 PrintString(fileString, fileLen, window->shell, window->filename);
1074 /* Free the text buffer copy returned from XmTextGetString */
1075 XtFree(fileString);
1079 ** Print a string (length is required). parent is the dialog parent, for
1080 ** error dialogs, and jobName is the print title.
1082 void PrintString(const char *string, int length, Widget parent, const char *jobName)
1084 char tmpFileName[L_tmpnam]; /* L_tmpnam defined in stdio.h */
1085 FILE *fp;
1086 int fd;
1088 /* Generate a temporary file name */
1089 /* If the glibc is used, the linker issues a warning at this point. This is
1090 very thoughtful of him, but does not apply to NEdit. The recommended
1091 replacement mkstemp(3) uses the same algorithm as NEdit, namely
1092 1. Create a filename
1093 2. Open the file with the O_CREAT|O_EXCL flags
1094 So all an attacker can do is a DoS on the print function. */
1095 tmpnam(tmpFileName);
1097 /* open the temporary file */
1098 #ifdef VMS
1099 if ((fp = fopen(tmpFileName, "w", "rfm = stmlf")) == NULL)
1100 #else
1101 if ((fd = open(tmpFileName, O_CREAT|O_EXCL|O_WRONLY, S_IRUSR | S_IWUSR)) < 0 || (fp = fdopen(fd, "w")) == NULL)
1102 #endif /* VMS */
1104 DialogF(DF_WARN, parent, 1, "Unable to write file for printing:\n%s",
1105 "Dismiss", errorString());
1106 return;
1109 #ifdef VMS
1110 chmod(tmpFileName, S_IRUSR | S_IWUSR);
1111 #endif
1113 /* write to the file */
1114 #ifdef IBM_FWRITE_BUG
1115 write(fileno(fp), string, length);
1116 #else
1117 fwrite(string, sizeof(char), length, fp);
1118 #endif
1119 if (ferror(fp)) {
1120 DialogF(DF_ERR, parent, 1, "%s not printed:\n%s", "Dismiss",
1121 jobName, errorString());
1122 fclose(fp); /* should call close(fd) in turn! */
1123 remove(tmpFileName);
1124 return;
1127 /* close the temporary file */
1128 if (fclose(fp) != 0) {
1129 DialogF(DF_ERR, parent, 1, "Error closing temp. print file:\n%s",
1130 "Dismiss", errorString());
1131 remove(tmpFileName);
1132 return;
1135 /* Print the temporary file, then delete it and return success */
1136 #ifdef VMS
1137 strcat(tmpFileName, ".");
1138 PrintFile(parent, tmpFileName, jobName, True);
1139 #else
1140 PrintFile(parent, tmpFileName, jobName);
1141 remove(tmpFileName);
1142 #endif /*VMS*/
1143 return;
1147 ** Wrapper for GetExistingFilename which uses the current window's path
1148 ** (if set) as the default directory.
1150 int PromptForExistingFile(WindowInfo *window, char *prompt, char *fullname)
1152 char *savedDefaultDir;
1153 int retVal;
1155 /* Temporarily set default directory to window->path, prompt for file,
1156 then, if the call was unsuccessful, restore the original default
1157 directory */
1158 savedDefaultDir = GetFileDialogDefaultDirectory();
1159 if (*window->path != '\0')
1160 SetFileDialogDefaultDirectory(window->path);
1161 retVal = GetExistingFilename(window->shell, prompt, fullname);
1162 if (retVal != GFN_OK)
1163 SetFileDialogDefaultDirectory(savedDefaultDir);
1164 if (savedDefaultDir != NULL)
1165 XtFree(savedDefaultDir);
1166 return retVal;
1170 ** Wrapper for GetNewFilename which uses the current window's path
1171 ** (if set) as the default directory, and asks about embedding newlines
1172 ** to make wrapping permanent.
1174 int PromptForNewFile(WindowInfo *window, char *prompt, char *fullname,
1175 int *fileFormat, int *addWrap)
1177 int n, retVal;
1178 Arg args[20];
1179 XmString s1, s2;
1180 Widget fileSB, wrapToggle;
1181 Widget formatForm, formatBtns, unixFormat, dosFormat, macFormat;
1182 char *savedDefaultDir;
1184 *fileFormat = window->fileFormat;
1186 /* Temporarily set default directory to window->path, prompt for file,
1187 then, if the call was unsuccessful, restore the original default
1188 directory */
1189 savedDefaultDir = GetFileDialogDefaultDirectory();
1190 if (*window->path != '\0')
1191 SetFileDialogDefaultDirectory(window->path);
1193 /* Present a file selection dialog with an added field for requesting
1194 long line wrapping to become permanent via inserted newlines */
1195 n = 0;
1196 XtSetArg(args[n],
1197 XmNselectionLabelString,
1198 s1 = XmStringCreateLocalized("New File Name:")); n++;
1199 XtSetArg(args[n], XmNdialogStyle, XmDIALOG_FULL_APPLICATION_MODAL); n++;
1200 XtSetArg(args[n],
1201 XmNdialogTitle,
1202 s2 = XmStringCreateSimple(prompt)); n++;
1203 XtSetArg(args[n], XmNresizePolicy, XmRESIZE_GROW); n++;
1204 fileSB = CreateFileSelectionDialog(window->shell,"FileSelect",args,n);
1205 XmStringFree(s1);
1206 XmStringFree(s2);
1207 formatForm = XtVaCreateManagedWidget("formatBtns", xmFormWidgetClass,
1208 fileSB, NULL);
1209 formatBtns = XtVaCreateManagedWidget("formatBtns", xmRowColumnWidgetClass,
1210 formatForm,
1211 XmNradioBehavior, XmONE_OF_MANY,
1212 XmNorientation, XmHORIZONTAL,
1213 XmNpacking, XmPACK_TIGHT,
1214 XmNtopAttachment, XmATTACH_FORM,
1215 XmNleftAttachment, XmATTACH_FORM, NULL);
1216 XtVaCreateManagedWidget("formatBtns", xmLabelWidgetClass, formatBtns,
1217 XmNlabelString, s1=XmStringCreateSimple("Format:"), NULL);
1218 XmStringFree(s1);
1219 unixFormat = XtVaCreateManagedWidget("unixFormat",
1220 xmToggleButtonWidgetClass, formatBtns, XmNlabelString,
1221 s1=XmStringCreateSimple("Unix"),
1222 XmNset, *fileFormat == UNIX_FILE_FORMAT,
1223 XmNuserData, UNIX_FILE_FORMAT,
1224 XmNmarginHeight, 0, XmNalignment, XmALIGNMENT_BEGINNING,
1225 XmNmnemonic, 'U', NULL);
1226 XmStringFree(s1);
1227 XtAddCallback(unixFormat, XmNvalueChangedCallback, setFormatCB,
1228 fileFormat);
1229 dosFormat = XtVaCreateManagedWidget("dosFormat",
1230 xmToggleButtonWidgetClass, formatBtns, XmNlabelString,
1231 s1=XmStringCreateSimple("DOS"),
1232 XmNset, *fileFormat == DOS_FILE_FORMAT,
1233 XmNuserData, DOS_FILE_FORMAT,
1234 XmNmarginHeight, 0, XmNalignment, XmALIGNMENT_BEGINNING,
1235 XmNmnemonic, 'O', NULL);
1236 XmStringFree(s1);
1237 XtAddCallback(dosFormat, XmNvalueChangedCallback, setFormatCB,
1238 fileFormat);
1239 macFormat = XtVaCreateManagedWidget("macFormat",
1240 xmToggleButtonWidgetClass, formatBtns, XmNlabelString,
1241 s1=XmStringCreateSimple("Macintosh"),
1242 XmNset, *fileFormat == MAC_FILE_FORMAT,
1243 XmNuserData, MAC_FILE_FORMAT,
1244 XmNmarginHeight, 0, XmNalignment, XmALIGNMENT_BEGINNING,
1245 XmNmnemonic, 'M', NULL);
1246 XmStringFree(s1);
1247 XtAddCallback(macFormat, XmNvalueChangedCallback, setFormatCB,
1248 fileFormat);
1249 if (window->wrapMode == CONTINUOUS_WRAP) {
1250 wrapToggle = XtVaCreateManagedWidget("addWrap",
1251 xmToggleButtonWidgetClass, formatForm, XmNlabelString,
1252 s1=XmStringCreateSimple("Add line breaks where wrapped"),
1253 XmNalignment, XmALIGNMENT_BEGINNING,
1254 XmNmnemonic, 'A',
1255 XmNtopAttachment, XmATTACH_WIDGET,
1256 XmNtopWidget, formatBtns,
1257 XmNleftAttachment, XmATTACH_FORM, NULL);
1258 XtAddCallback(wrapToggle, XmNvalueChangedCallback, addWrapCB,
1259 addWrap);
1260 XmStringFree(s1);
1262 *addWrap = False;
1263 XtVaSetValues(XmFileSelectionBoxGetChild(fileSB,
1264 XmDIALOG_FILTER_LABEL), XmNmnemonic, 'l', XmNuserData,
1265 XmFileSelectionBoxGetChild(fileSB, XmDIALOG_FILTER_TEXT), NULL);
1266 XtVaSetValues(XmFileSelectionBoxGetChild(fileSB,
1267 XmDIALOG_DIR_LIST_LABEL), XmNmnemonic, 'D', XmNuserData,
1268 XmFileSelectionBoxGetChild(fileSB, XmDIALOG_DIR_LIST), NULL);
1269 XtVaSetValues(XmFileSelectionBoxGetChild(fileSB,
1270 XmDIALOG_LIST_LABEL), XmNmnemonic, 'F', XmNuserData,
1271 XmFileSelectionBoxGetChild(fileSB, XmDIALOG_LIST), NULL);
1272 XtVaSetValues(XmFileSelectionBoxGetChild(fileSB,
1273 XmDIALOG_SELECTION_LABEL), XmNmnemonic,
1274 prompt[strspn(prompt, "lFD")], XmNuserData,
1275 XmFileSelectionBoxGetChild(fileSB, XmDIALOG_TEXT), NULL);
1276 AddDialogMnemonicHandler(fileSB, FALSE);
1277 RemapDeleteKey(XmFileSelectionBoxGetChild(fileSB, XmDIALOG_FILTER_TEXT));
1278 RemapDeleteKey(XmFileSelectionBoxGetChild(fileSB, XmDIALOG_TEXT));
1279 retVal = HandleCustomNewFileSB(fileSB, fullname,
1280 window->filenameSet ? window->filename : NULL);
1282 if (retVal != GFN_OK)
1283 SetFileDialogDefaultDirectory(savedDefaultDir);
1284 if (savedDefaultDir != NULL)
1285 XtFree(savedDefaultDir);
1286 return retVal;
1290 ** Find a name for an untitled file, unique in the name space of in the opened
1291 ** files in this session, i.e. Untitled or Untitled_nn, and write it into
1292 ** the string "name".
1294 void UniqueUntitledName(char *name)
1296 WindowInfo *w;
1297 int i;
1299 for (i=0; i<INT_MAX; i++) {
1300 if (i == 0)
1301 sprintf(name, "Untitled");
1302 else
1303 sprintf(name, "Untitled_%d", i);
1304 for (w=WindowList; w!=NULL; w=w->next)
1305 if (!strcmp(w->filename, name))
1306 break;
1307 if (w == NULL)
1308 break;
1313 ** Check if the file in the window was changed by an external source.
1314 ** and put up a warning dialog if it has.
1316 void CheckForChangesToFile(WindowInfo *window)
1318 static WindowInfo *lastCheckWindow;
1319 static Time lastCheckTime = 0;
1320 char fullname[MAXPATHLEN];
1321 struct stat statbuf;
1322 Time timestamp;
1323 FILE *fp;
1324 int resp;
1325 XWindowAttributes winAttr;
1327 if(!window->filenameSet)
1328 return;
1330 /* If last check was very recent, don't impact performance */
1331 timestamp = XtLastTimestampProcessed(XtDisplay(window->shell));
1332 if (window == lastCheckWindow &&
1333 timestamp - lastCheckTime < MOD_CHECK_INTERVAL)
1334 return;
1335 lastCheckWindow = window;
1336 lastCheckTime = timestamp;
1338 /* Get the file mode and modification time */
1339 strcpy(fullname, window->path);
1340 strcat(fullname, window->filename);
1341 if (stat(fullname, &statbuf) != 0) {
1342 /* Can't stat the file -- maybe it's been deleted.
1343 The filename is now invalid */
1344 window->filenameSet = FALSE;
1345 /* The buffer's "modified" since there's no longer a backup on disk */
1346 SetWindowModified(window, TRUE);
1347 /* Undo should not restore the "Unmodified" property of the window */
1348 DisableUnmodified(window);
1349 /* Warn the user, if they like to be warned (Maybe this should be its
1350 own preference setting: GetPrefWarnFileDeleted() ) */
1351 if (window->lastModTime != 0 && GetPrefWarnFileMods()) {
1352 window->lastModTime = 0;
1353 /* See note below about pop-up timing and XUngrabPointer */
1354 XUngrabPointer(XtDisplay(window->shell), timestamp);
1355 if( errno == EACCES )
1356 resp = DialogF( DF_ERR, window->shell, 2,
1357 "You no longer have access to file \"%s\".\n"
1358 "Another program may have changed the permissions one of\n"
1359 "its parent directories.\n"
1360 "Save as a new file?",
1361 "Save As...", "Dismiss",
1362 window->filename );
1363 else
1364 resp = DialogF( DF_ERR, window->shell, 2,
1365 "Error while checking the status of file \"%s\":\n"
1366 " \"%s\"\n"
1367 "Another program may have deleted or moved it.\n"
1368 "Save as a new file?",
1369 "Save As...", "Dismiss",
1370 window->filename, errorString() );
1371 if (resp == 1)
1372 SaveWindowAs(window, NULL, 0);
1374 return;
1377 /* Check that the file's read-only status is still correct (but
1378 only if the file can still be opened successfully in read mode) */
1379 if (window->fileMode != statbuf.st_mode) {
1380 window->fileMode = statbuf.st_mode;
1381 if ((fp = fopen(fullname, "r")) != NULL) {
1382 int readOnly;
1383 fclose(fp);
1384 #ifdef USE_ACCESS
1385 readOnly = access(fullname, W_OK) != 0;
1386 #else
1387 if (((fp = fopen(fullname, "r+")) != NULL)) {
1388 readOnly = FALSE;
1389 fclose(fp);
1390 } else
1391 readOnly = TRUE;
1392 #endif
1393 if (IS_PERM_LOCKED(window->lockReasons) != readOnly) {
1394 SET_PERM_LOCKED(window->lockReasons, readOnly);
1395 UpdateWindowTitle(window);
1396 UpdateWindowReadOnly(window);
1401 /* Update the status, but don't pop up a dialog if we're called
1402 from a place where the window might be iconic (e.g., from the
1403 replace dialog) or on another desktop.
1405 This works, but I bet it costs a round-trip to the server.
1406 Might be better to capture MapNotify/Unmap events instead. */
1408 XGetWindowAttributes(XtDisplay(window->shell),
1409 XtWindow(window->shell),
1410 &winAttr);
1412 if (winAttr.map_state != IsViewable)
1413 return;
1415 /* Warn the user if the file has been modified, unless checking is
1416 turned off or the user has already been warned. Popping up a dialog
1417 from a focus callback (which is how this routine is usually called)
1418 seems to catch Motif off guard, and if the timing is just right, the
1419 dialog can be left with a still active pointer grab from a Motif menu
1420 which is still in the process of popping down. The workaround, below,
1421 of calling XUngrabPointer is inelegant but seems to fix the problem. */
1422 if (window->lastModTime != 0 && window->lastModTime != statbuf.st_mtime &&
1423 GetPrefWarnFileMods()) {
1424 window->lastModTime = 0; /* Inhibit further warnings */
1425 XUngrabPointer(XtDisplay(window->shell), timestamp);
1426 if (window->fileChanged)
1427 resp = DialogF(DF_WARN, window->shell, 2,
1428 "%s has been modified by another program. Reload?\n\n"
1429 "WARNING: Reloading will discard changes made in this\n"
1430 "editing session!",
1431 "Reload", "Dismiss", window->filename);
1432 else
1433 resp = DialogF(DF_WARN, window->shell, 2,
1434 "%s has been modified by another\nprogram. Reload?",
1435 "Reload", "Dismiss", window->filename);
1436 if (resp == 1)
1437 RevertToSaved(window);
1442 ** Return true if the file displayed in window has been modified externally
1443 ** to nedit. This should return FALSE if the file has been deleted or is
1444 ** unavailable.
1446 static int fileWasModifiedExternally(WindowInfo *window)
1448 char fullname[MAXPATHLEN];
1449 struct stat statbuf;
1451 if(!window->filenameSet)
1452 return FALSE;
1453 if (window->lastModTime == 0)
1454 return FALSE;
1455 strcpy(fullname, window->path);
1456 strcat(fullname, window->filename);
1457 if (stat(fullname, &statbuf) != 0)
1458 return FALSE;
1459 return window->lastModTime != statbuf.st_mtime;
1463 ** Check the read-only or locked status of the window and beep and return
1464 ** false if the window should not be written in.
1466 int CheckReadOnly(WindowInfo *window)
1468 if (IS_ANY_LOCKED(window->lockReasons)) {
1469 XBell(TheDisplay, 0);
1470 return True;
1472 return False;
1476 ** Wrapper for strerror so all the calls don't have to be ifdef'd for VMS.
1478 static char *errorString(void)
1480 #ifdef VMS
1481 return strerror(errno, vaxc$errno);
1482 #else
1483 return strerror(errno);
1484 #endif
1487 #ifdef VMS
1489 ** Removing the VMS version number from a file name (if has one).
1491 void removeVersionNumber(char *fileName)
1493 char *versionStart;
1495 versionStart = strrchr(fileName, ';');
1496 if (versionStart != NULL)
1497 *versionStart = '\0';
1499 #endif /*VMS*/
1502 ** Callback procedure for File Format toggle buttons. Format is stored
1503 ** in userData field of widget button
1505 static void setFormatCB(Widget w, XtPointer clientData, XtPointer callData)
1507 if (XmToggleButtonGetState(w))
1508 XtVaGetValues(w, XmNuserData, clientData, NULL);
1512 ** Callback procedure for toggle button requesting newlines to be inserted
1513 ** to emulate continuous wrapping.
1515 static void addWrapCB(Widget w, XtPointer clientData, XtPointer callData)
1517 int resp;
1518 int *addWrap = (int *)clientData;
1520 if (XmToggleButtonGetState(w)) {
1521 resp = DialogF(DF_WARN, w, 2,
1522 "This operation adds permanent line breaks to\n\
1523 match the automatic wrapping done by the\n\
1524 Continuous Wrap mode Preferences Option.\n\
1526 *** This Option is Irreversable ***\n\
1528 Once newlines are inserted, continuous wrapping\n\
1529 will no longer work automatically on these lines", "OK", "Cancel");
1530 if (resp == 2) {
1531 XmToggleButtonSetState(w, False, False);
1532 *addWrap = False;
1533 } else
1534 *addWrap = True;
1535 } else
1536 *addWrap = False;
1540 ** Change a window created in NEdit's continuous wrap mode to the more
1541 ** conventional Unix format of embedded newlines. Indicate to the user
1542 ** by turning off Continuous Wrap mode.
1544 static void addWrapNewlines(WindowInfo *window)
1546 int fileLen, i, insertPositions[MAX_PANES], topLines[MAX_PANES];
1547 int horizOffset;
1548 Widget text;
1549 char *fileString;
1551 /* save the insert and scroll positions of each pane */
1552 for (i=0; i<=window->nPanes; i++) {
1553 text = i==0 ? window->textArea : window->textPanes[i-1];
1554 insertPositions[i] = TextGetCursorPos(text);
1555 TextGetScroll(text, &topLines[i], &horizOffset);
1558 /* Modify the buffer to add wrapping */
1559 fileString = TextGetWrapped(window->textArea, 0,
1560 window->buffer->length, &fileLen);
1561 BufSetAll(window->buffer, fileString);
1562 XtFree(fileString);
1564 /* restore the insert and scroll positions of each pane */
1565 for (i=0; i<=window->nPanes; i++) {
1566 text = i==0 ? window->textArea : window->textPanes[i-1];
1567 TextSetCursorPos(text, insertPositions[i]);
1568 TextSetScroll(text, topLines[i], 0);
1571 /* Show the user that something has happened by turning off
1572 Continuous Wrap mode */
1573 XmToggleButtonSetState(window->continuousWrapItem, False, True);
1577 ** Samples up to a maximum of FORMAT_SAMPLE_LINES lines and FORMAT_SAMPLE_CHARS
1578 ** characters, to determine whether fileString represents a MS DOS or Macintosh
1579 ** format file. If there's ANY ambiguity (a newline in the sample not paired
1580 ** with a return in an otherwise DOS looking file, or a newline appearing in
1581 ** the sampled portion of a Macintosh looking file), the file is judged to be
1582 ** Unix format.
1584 static int formatOfFile(const char *fileString)
1586 const char *p;
1587 int nNewlines = 0, nReturns = 0;
1589 for (p=fileString; *p!='\0' && p < fileString + FORMAT_SAMPLE_CHARS; p++) {
1590 if (*p == '\n') {
1591 nNewlines++;
1592 if (p == fileString || *(p-1) != '\r')
1593 return UNIX_FILE_FORMAT;
1594 if (nNewlines >= FORMAT_SAMPLE_LINES)
1595 return DOS_FILE_FORMAT;
1596 } else if (*p == '\r')
1597 nReturns++;
1599 if (nNewlines > 0)
1600 return DOS_FILE_FORMAT;
1601 if (nReturns > 1)
1602 return MAC_FILE_FORMAT;
1603 return UNIX_FILE_FORMAT;
1607 ** Converts a string (which may represent the entire contents of the file)
1608 ** from DOS or Macintosh format to Unix format. Conversion is done in-place.
1609 ** In the DOS case, the length will be shorter, and passed length will be
1610 ** modified to reflect the new length.
1612 static void convertFromDosFileString(char *fileString, int *length)
1614 char *outPtr = fileString;
1615 char *inPtr = fileString;
1616 while (inPtr < fileString + *length) {
1617 if (*inPtr == '\r' && *(inPtr + 1) == '\n')
1618 inPtr++;
1619 *outPtr++ = *inPtr++;
1621 *outPtr = '\0';
1622 *length = outPtr - fileString;
1624 static void convertFromMacFileString(char *fileString, int length)
1626 char *inPtr = fileString;
1627 while (inPtr < fileString + length) {
1628 if (*inPtr == '\r' )
1629 *inPtr = '\n';
1630 inPtr++;
1635 ** Converts a string (which may represent the entire contents of the file) from
1636 ** Unix to DOS format. String is re-allocated (with malloc), and length is
1637 ** modified. If allocation fails, which it may, because this can potentially
1638 ** be a huge hunk of memory, returns FALSE and no conversion is done.
1640 ** This could be done more efficiently by asking doSave to allocate some
1641 ** extra memory for this, and only re-allocating if it wasn't enough. If
1642 ** anyone cares about the performance or the potential for running out of
1643 ** memory on a save, it should probably be redone.
1645 static int convertToDosFileString(char **fileString, int *length)
1647 char *outPtr, *outString;
1648 char *inPtr = *fileString;
1649 int inLength = *length;
1650 int outLength = 0;
1652 /* How long a string will we need? */
1653 while (inPtr < *fileString + inLength) {
1654 if (*inPtr == '\n')
1655 outLength++;
1656 inPtr++;
1657 outLength++;
1660 /* Allocate the new string */
1661 outString = (char *)malloc(outLength + 1);
1662 if (outString == NULL)
1663 return FALSE;
1665 /* Do the conversion, free the old string */
1666 inPtr = *fileString;
1667 outPtr = outString;
1668 while (inPtr < *fileString + inLength) {
1669 if (*inPtr == '\n')
1670 *outPtr++ = '\r';
1671 *outPtr++ = *inPtr++;
1673 *outPtr = '\0';
1674 free(*fileString);
1675 *fileString = outString;
1676 *length = outLength;
1677 return TRUE;
1681 ** Converts a string (which may represent the entire contents of the file)
1682 ** from Unix to Macintosh format.
1684 static void convertToMacFileString(char *fileString, int length)
1686 char *inPtr = fileString;
1688 while (inPtr < fileString + length) {
1689 if (*inPtr == '\n' )
1690 *inPtr = '\r';
1691 inPtr++;