Fix for SF bug #1908890: window title not updated.
[nedit.git] / source / file.c
blobdfc6aa54b60f3172f881e7e5f90b4334b7f79d8f
1 static const char CVSID[] = "$Id: file.c,v 1.117 2008/03/06 17:24:22 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 <errno.h>
52 #include <limits.h>
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #include <unistd.h>
58 #ifdef VMS
59 #include "../util/VMSparam.h"
60 #include <types.h>
61 #include <stat.h>
62 #include <unixio.h>
63 #else
64 #include <sys/types.h>
65 #include <sys/stat.h>
66 #ifndef __MVS__
67 #include <sys/param.h>
68 #endif
69 #include <fcntl.h>
70 #endif /*VMS*/
72 #include <Xm/Xm.h>
73 #include <Xm/ToggleB.h>
74 #include <Xm/FileSB.h>
75 #include <Xm/RowColumn.h>
76 #include <Xm/Form.h>
77 #include <Xm/Label.h>
79 #ifdef HAVE_DEBUG_H
80 #include "../debug.h"
81 #endif
83 /* Maximum frequency in miliseconds of checking for external modifications.
84 The periodic check is only performed on buffer modification, and the check
85 interval is only to prevent checking on every keystroke in case of a file
86 system which is slow to process stat requests (which I'm not sure exists) */
87 #define MOD_CHECK_INTERVAL 3000
89 static int doSave(WindowInfo *window);
90 static void safeClose(WindowInfo *window);
91 static int doOpen(WindowInfo *window, const char *name, const char *path,
92 int flags);
93 static void backupFileName(WindowInfo *window, char *name, size_t len);
94 static int writeBckVersion(WindowInfo *window);
95 static int bckError(WindowInfo *window, const char *errString, const char *file);
96 static int fileWasModifiedExternally(WindowInfo *window);
97 static const char *errorString(void);
98 static void addWrapNewlines(WindowInfo *window);
99 static void setFormatCB(Widget w, XtPointer clientData, XtPointer callData);
100 static void addWrapCB(Widget w, XtPointer clientData, XtPointer callData);
101 static int cmpWinAgainstFile(WindowInfo *window, const char *fileName);
102 static int min(int i1, int i2);
103 static void modifiedWindowDestroyedCB(Widget w, XtPointer clientData,
104 XtPointer callData);
105 static void forceShowLineNumbers(WindowInfo *window);
107 #ifdef VMS
108 void removeVersionNumber(char *fileName);
109 #endif /*VMS*/
111 WindowInfo *EditNewFile(WindowInfo *inWindow, char *geometry, int iconic,
112 const char *languageMode, const char *defaultPath)
114 char name[MAXPATHLEN];
115 WindowInfo *window;
117 /*... test for creatability? */
119 /* Find a (relatively) unique name for the new file */
120 UniqueUntitledName(name);
122 /* create new window/document */
123 if (inWindow)
124 window = CreateDocument(inWindow, name);
125 else
126 window = CreateWindow(name, geometry, iconic);
128 strcpy(window->filename, name);
129 strcpy(window->path, defaultPath ? defaultPath : "");
130 SetWindowModified(window, FALSE);
131 CLEAR_ALL_LOCKS(window->lockReasons);
132 UpdateWindowReadOnly(window);
133 UpdateStatsLine(window);
134 UpdateWindowTitle(window);
135 RefreshTabState(window);
137 if (languageMode == NULL)
138 DetermineLanguageMode(window, True);
139 else
140 SetLanguageMode(window, FindLanguageMode(languageMode), True);
142 ShowTabBar(window, GetShowTabBar(window));
144 if (iconic && IsIconic(window))
145 RaiseDocument(window);
146 else
147 RaiseDocumentWindow(window);
149 SortTabBar(window);
150 return window;
154 ** Open an existing file specified by name and path. Use the window inWindow
155 ** unless inWindow is NULL or points to a window which is already in use
156 ** (displays a file other than Untitled, or is Untitled but modified). Flags
157 ** can be any of:
159 ** CREATE: If file is not found, (optionally) prompt the
160 ** user whether to create
161 ** SUPPRESS_CREATE_WARN When creating a file, don't ask the user
162 ** PREF_READ_ONLY Make the file read-only regardless
164 ** If languageMode is passed as NULL, it will be determined automatically
165 ** from the file extension or file contents.
167 ** If bgOpen is True, then the file will be open in background. This
168 ** works in association with the SetLanguageMode() function that has
169 ** the syntax highlighting deferred, in order to speed up the file-
170 ** opening operation when multiple files are being opened in succession.
172 WindowInfo *EditExistingFile(WindowInfo *inWindow, const char *name,
173 const char *path, int flags, char *geometry, int iconic,
174 const char *languageMode, int tabbed, int bgOpen)
176 WindowInfo *window;
177 char fullname[MAXPATHLEN];
179 /* first look to see if file is already displayed in a window */
180 window = FindWindowWithFile(name, path);
181 if (window != NULL) {
182 if (!bgOpen) {
183 if (iconic)
184 RaiseDocument(window);
185 else
186 RaiseDocumentWindow(window);
188 return window;
191 /* If an existing window isn't specified; or the window is already
192 in use (not Untitled or Untitled and modified), or is currently
193 busy running a macro; create the window */
194 if (inWindow == NULL) {
195 window = CreateWindow(name, geometry, iconic);
197 else if (inWindow->filenameSet || inWindow->fileChanged ||
198 inWindow->macroCmdData != NULL) {
199 if (tabbed) {
200 window = CreateDocument(inWindow, name);
202 else {
203 window = CreateWindow(name, geometry, iconic);
206 else {
207 /* open file in untitled document */
208 window = inWindow;
209 strcpy(window->path, path);
210 strcpy(window->filename, name);
211 if (!iconic && !bgOpen) {
212 RaiseDocumentWindow(window);
216 /* Open the file */
217 if (!doOpen(window, name, path, flags)) {
218 /* The user may have destroyed the window instead of closing the
219 warning dialog; don't close it twice */
220 safeClose(window);
222 return NULL;
224 forceShowLineNumbers(window);
226 /* Decide what language mode to use, trigger language specific actions */
227 if (languageMode == NULL)
228 DetermineLanguageMode(window, True);
229 else
230 SetLanguageMode(window, FindLanguageMode(languageMode), True);
232 /* update tab label and tooltip */
233 RefreshTabState(window);
234 SortTabBar(window);
235 ShowTabBar(window, GetShowTabBar(window));
237 if (!bgOpen)
238 RaiseDocument(window);
240 /* Bring the title bar and statistics line up to date, doOpen does
241 not necessarily set the window title or read-only status */
242 UpdateWindowTitle(window);
243 UpdateWindowReadOnly(window);
244 UpdateStatsLine(window);
246 /* Add the name to the convenience menu of previously opened files */
247 strcpy(fullname, path);
248 strcat(fullname, name);
249 if(GetPrefAlwaysCheckRelTagsSpecs())
250 AddRelTagsFile(GetPrefTagFile(), path, TAG);
251 AddToPrevOpenMenu(fullname);
253 return window;
256 void RevertToSaved(WindowInfo *window)
258 char name[MAXPATHLEN], path[MAXPATHLEN];
259 int i;
260 int insertPositions[MAX_PANES], topLines[MAX_PANES];
261 int horizOffsets[MAX_PANES];
262 int openFlags = 0;
263 Widget text;
265 /* Can't revert untitled windows */
266 if (!window->filenameSet)
268 DialogF(DF_WARN, window->shell, 1, "Error",
269 "Window '%s' was never saved, can't re-read", "OK",
270 window->filename);
271 return;
274 /* save insert & scroll positions of all of the panes to restore later */
275 for (i=0; i<=window->nPanes; i++) {
276 text = i==0 ? window->textArea : window->textPanes[i-1];
277 insertPositions[i] = TextGetCursorPos(text);
278 TextGetScroll(text, &topLines[i], &horizOffsets[i]);
281 /* re-read the file, update the window title if new file is different */
282 strcpy(name, window->filename);
283 strcpy(path, window->path);
284 RemoveBackupFile(window);
285 ClearUndoList(window);
286 openFlags |= IS_USER_LOCKED(window->lockReasons) ? PREF_READ_ONLY : 0;
287 if (!doOpen(window, name, path, openFlags)) {
288 /* This is a bit sketchy. The only error in doOpen that irreperably
289 damages the window is "too much binary data". It should be
290 pretty rare to be reverting something that was fine only to find
291 that now it has too much binary data. */
292 if (!window->fileMissing)
293 safeClose(window);
294 else {
295 /* Treat it like an externally modified file */
296 window->lastModTime=0;
297 window->fileMissing=FALSE;
299 return;
301 forceShowLineNumbers(window);
302 UpdateWindowTitle(window);
303 UpdateWindowReadOnly(window);
305 /* restore the insert and scroll positions of each pane */
306 for (i=0; i<=window->nPanes; i++) {
307 text = i==0 ? window->textArea : window->textPanes[i-1];
308 TextSetCursorPos(text, insertPositions[i]);
309 TextSetScroll(text, topLines[i], horizOffsets[i]);
314 ** Checks whether a window is still alive, and closes it only if so.
315 ** Intended to be used when the file could not be opened for some reason.
316 ** Normally the window is still alive, but the user may have closed the
317 ** window instead of the error dialog. In that case, we shouldn't close the
318 ** window a second time.
320 static void safeClose(WindowInfo *window)
322 WindowInfo* p = WindowList;
323 while(p) {
324 if (p == window) {
325 CloseWindow(window);
326 return;
328 p = p->next;
332 static int doOpen(WindowInfo *window, const char *name, const char *path,
333 int flags)
335 char fullname[MAXPATHLEN];
336 struct stat statbuf;
337 int fileLen, readLen;
338 char *fileString, *c;
339 FILE *fp = NULL;
340 int fd;
341 int resp;
343 /* initialize lock reasons */
344 CLEAR_ALL_LOCKS(window->lockReasons);
346 /* Update the window data structure */
347 strcpy(window->filename, name);
348 strcpy(window->path, path);
349 window->filenameSet = TRUE;
350 window->fileMissing = TRUE;
352 /* Get the full name of the file */
353 strcpy(fullname, path);
354 strcat(fullname, name);
356 /* Open the file */
357 #ifndef DONT_USE_ACCESS
358 /* The only advantage of this is if you use clearcase,
359 which messes up the mtime of files opened with r+,
360 even if they're never actually written.
361 To avoid requiring special builds for clearcase users,
362 this is now the default. */
364 if ((fp = fopen(fullname, "r")) != NULL) {
365 if(access(fullname, W_OK) != 0)
366 SET_PERM_LOCKED(window->lockReasons, TRUE);
367 #else
368 fp = fopen(fullname, "rb+");
369 if (fp == NULL) {
370 /* Error opening file or file is not writeable */
371 fp = fopen(fullname, "rb");
372 if (fp != NULL) {
373 /* File is read only */
374 SET_PERM_LOCKED(window->lockReasons, TRUE);
375 #endif
376 } else if (flags & CREATE && errno == ENOENT) {
377 /* Give option to create (or to exit if this is the only window) */
378 if (!(flags & SUPPRESS_CREATE_WARN)) {
379 /* on Solaris 2.6, and possibly other OSes, dialog won't
380 show if parent window is iconized. */
381 RaiseShellWindow(window->shell, False);
383 /* ask user for next action if file not found */
384 if (WindowList == window && window->next == NULL) {
385 resp = DialogF(DF_WARN, window->shell, 3, "New File",
386 "Can't open %s:\n%s", "New File", "Cancel",
387 "Exit NEdit", fullname, errorString());
389 else {
390 resp = DialogF(DF_WARN, window->shell, 2, "New File",
391 "Can't open %s:\n%s", "New File", "Cancel", fullname,
392 errorString());
395 if (resp == 2) {
396 return FALSE;
398 else if (resp == 3) {
399 exit(EXIT_SUCCESS);
403 /* Test if new file can be created */
404 if ((fd = creat(fullname, 0666)) == -1) {
405 DialogF(DF_ERR, window->shell, 1, "Error creating File",
406 "Can't create %s:\n%s", "OK", fullname, errorString());
407 return FALSE;
409 else {
410 #ifdef VMS
411 /* get correct version number and close before removing */
412 getname(fd, fullname);
413 #endif
414 close(fd);
415 remove(fullname);
418 SetWindowModified(window, FALSE);
419 if ((flags & PREF_READ_ONLY) != 0) {
420 SET_USER_LOCKED(window->lockReasons, TRUE);
422 UpdateWindowReadOnly(window);
423 return TRUE;
425 else {
426 /* A true error */
427 DialogF(DF_ERR, window->shell, 1, "Error opening File",
428 "Could not open %s%s:\n%s", "OK", path, name,
429 errorString());
430 return FALSE;
434 /* Get the length of the file, the protection mode, and the time of the
435 last modification to the file */
436 if (fstat(fileno(fp), &statbuf) != 0) {
437 fclose(fp);
438 window->filenameSet = FALSE; /* Temp. prevent check for changes. */
439 DialogF(DF_ERR, window->shell, 1, "Error opening File",
440 "Error opening %s", "OK", name);
441 window->filenameSet = TRUE;
442 return FALSE;
445 if (S_ISDIR(statbuf.st_mode)) {
446 fclose(fp);
447 window->filenameSet = FALSE; /* Temp. prevent check for changes. */
448 DialogF(DF_ERR, window->shell, 1, "Error opening File",
449 "Can't open directory %s", "OK", name);
450 window->filenameSet = TRUE;
451 return FALSE;
454 #ifdef S_ISBLK
455 if (S_ISBLK(statbuf.st_mode)) {
456 fclose(fp);
457 window->filenameSet = FALSE; /* Temp. prevent check for changes. */
458 DialogF(DF_ERR, window->shell, 1, "Error opening File",
459 "Can't open block device %s", "OK", name);
460 window->filenameSet = TRUE;
461 return FALSE;
463 #endif
464 fileLen = statbuf.st_size;
466 /* Allocate space for the whole contents of the file (unfortunately) */
467 fileString = (char *)malloc(fileLen+1); /* +1 = space for null */
468 if (fileString == NULL) {
469 fclose(fp);
470 window->filenameSet = FALSE; /* Temp. prevent check for changes. */
471 DialogF(DF_ERR, window->shell, 1, "Error while opening File",
472 "File is too large to edit", "OK");
473 window->filenameSet = TRUE;
474 return FALSE;
477 /* Read the file into fileString and terminate with a null */
478 readLen = fread(fileString, sizeof(char), fileLen, fp);
479 if (ferror(fp)) {
480 fclose(fp);
481 window->filenameSet = FALSE; /* Temp. prevent check for changes. */
482 DialogF(DF_ERR, window->shell, 1, "Error while opening File",
483 "Error reading %s:\n%s", "OK", name, errorString());
484 window->filenameSet = TRUE;
485 free(fileString);
486 return FALSE;
488 fileString[readLen] = 0;
490 /* Close the file */
491 if (fclose(fp) != 0) {
492 /* unlikely error */
493 DialogF(DF_WARN, window->shell, 1, "Error while opening File",
494 "Unable to close file", "OK");
495 /* we read it successfully, so continue */
498 /* Any errors that happen after this point leave the window in a
499 "broken" state, and thus RevertToSaved will abandon the window if
500 window->fileMissing is FALSE and doOpen fails. */
501 window->fileMode = statbuf.st_mode;
502 window->fileUid = statbuf.st_uid;
503 window->fileGid = statbuf.st_gid;
504 window->lastModTime = statbuf.st_mtime;
505 window->device = statbuf.st_dev;
506 window->inode = statbuf.st_ino;
507 window->fileMissing = FALSE;
509 /* Detect and convert DOS and Macintosh format files */
510 if (GetPrefForceOSConversion()) {
511 window->fileFormat = FormatOfFile(fileString);
512 if (window->fileFormat == DOS_FILE_FORMAT) {
513 ConvertFromDosFileString(fileString, &readLen, NULL);
514 } else if (window->fileFormat == MAC_FILE_FORMAT) {
515 ConvertFromMacFileString(fileString, readLen);
519 /* Display the file contents in the text widget */
520 window->ignoreModify = True;
521 BufSetAll(window->buffer, fileString);
522 window->ignoreModify = False;
524 /* Check that the length that the buffer thinks it has is the same
525 as what we gave it. If not, there were probably nuls in the file.
526 Substitute them with another character. If that is impossible, warn
527 the user, make the file read-only, and force a substitution */
528 if (window->buffer->length != readLen) {
529 if (!BufSubstituteNullChars(fileString, readLen, window->buffer)) {
530 resp = DialogF(DF_ERR, window->shell, 2, "Error while opening File",
531 "Too much binary data in file. You may view\n"
532 "it, but not modify or re-save its contents.", "View",
533 "Cancel");
534 if (resp == 2) {
535 return FALSE;
538 SET_TMBD_LOCKED(window->lockReasons, TRUE);
539 for (c = fileString; c < &fileString[readLen]; c++) {
540 if (*c == '\0') {
541 *c = (char) 0xfe;
544 window->buffer->nullSubsChar = (char) 0xfe;
546 window->ignoreModify = True;
547 BufSetAll(window->buffer, fileString);
548 window->ignoreModify = False;
551 /* Release the memory that holds fileString */
552 free(fileString);
554 /* Set window title and file changed flag */
555 if ((flags & PREF_READ_ONLY) != 0) {
556 SET_USER_LOCKED(window->lockReasons, TRUE);
558 if (IS_PERM_LOCKED(window->lockReasons)) {
559 window->fileChanged = FALSE;
560 UpdateWindowTitle(window);
561 } else {
562 SetWindowModified(window, FALSE);
563 if (IS_ANY_LOCKED(window->lockReasons)) {
564 UpdateWindowTitle(window);
567 UpdateWindowReadOnly(window);
569 return TRUE;
572 int IncludeFile(WindowInfo *window, const char *name)
574 struct stat statbuf;
575 int fileLen, readLen;
576 char *fileString;
577 FILE *fp = NULL;
579 /* Open the file */
580 fp = fopen(name, "rb");
581 if (fp == NULL)
583 DialogF(DF_ERR, window->shell, 1, "Error opening File",
584 "Could not open %s:\n%s", "OK", name, errorString());
585 return FALSE;
588 /* Get the length of the file */
589 if (fstat(fileno(fp), &statbuf) != 0)
591 DialogF(DF_ERR, window->shell, 1, "Error opening File",
592 "Error opening %s", "OK", name);
593 fclose(fp);
594 return FALSE;
597 if (S_ISDIR(statbuf.st_mode))
599 DialogF(DF_ERR, window->shell, 1, "Error opening File",
600 "Can't open directory %s", "OK", name);
601 fclose(fp);
602 return FALSE;
604 fileLen = statbuf.st_size;
606 /* allocate space for the whole contents of the file */
607 fileString = (char *)malloc(fileLen+1); /* +1 = space for null */
608 if (fileString == NULL)
610 DialogF(DF_ERR, window->shell, 1, "Error opening File",
611 "File is too large to include", "OK");
612 fclose(fp);
613 return FALSE;
616 /* read the file into fileString and terminate with a null */
617 readLen = fread(fileString, sizeof(char), fileLen, fp);
618 if (ferror(fp))
620 DialogF(DF_ERR, window->shell, 1, "Error opening File",
621 "Error reading %s:\n%s", "OK", name, errorString());
622 fclose(fp);
623 free(fileString);
624 return FALSE;
626 fileString[readLen] = 0;
628 /* Detect and convert DOS and Macintosh format files */
629 switch (FormatOfFile(fileString)) {
630 case DOS_FILE_FORMAT:
631 ConvertFromDosFileString(fileString, &readLen, NULL);
632 break;
633 case MAC_FILE_FORMAT:
634 ConvertFromMacFileString(fileString, readLen);
635 break;
636 default:
637 /* Default is Unix, no conversion necessary. */
638 break;
641 /* If the file contained ascii nulls, re-map them */
642 if (!BufSubstituteNullChars(fileString, readLen, window->buffer))
644 DialogF(DF_ERR, window->shell, 1, "Error opening File",
645 "Too much binary data in file", "OK");
648 /* close the file */
649 if (fclose(fp) != 0)
651 /* unlikely error */
652 DialogF(DF_WARN, window->shell, 1, "Error opening File",
653 "Unable to close file", "OK");
654 /* we read it successfully, so continue */
657 /* insert the contents of the file in the selection or at the insert
658 position in the window if no selection exists */
659 if (window->buffer->primary.selected)
660 BufReplaceSelected(window->buffer, fileString);
661 else
662 BufInsert(window->buffer, TextGetCursorPos(window->lastFocus),
663 fileString);
665 /* release the memory that holds fileString */
666 free(fileString);
668 return TRUE;
672 ** Close all files and windows, leaving one untitled window
674 int CloseAllFilesAndWindows(void)
676 while (WindowList->next != NULL ||
677 WindowList->filenameSet || WindowList->fileChanged) {
679 * When we're exiting through a macro, the document running the
680 * macro does not disappear from the list, so we could get stuck
681 * in an endless loop if we try to close it. Therefore, we close
682 * other documents first. (Note that the document running the macro
683 * may get closed because it is in the same window as another
684 * document that gets closed, but it won't disappear; it becomes
685 * Untitled.)
687 if (WindowList == MacroRunWindow() && WindowList->next != NULL) {
688 if (!CloseAllDocumentInWindow(WindowList->next)) {
689 return False;
692 else {
693 if (!CloseAllDocumentInWindow(WindowList)) {
694 return False;
699 return TRUE;
702 int CloseFileAndWindow(WindowInfo *window, int preResponse)
704 int response, stat;
706 /* Make sure that the window is not in iconified state */
707 if (window->fileChanged)
708 RaiseDocumentWindow(window);
710 /* If the window is a normal & unmodified file or an empty new file,
711 or if the user wants to ignore external modifications then
712 just close it. Otherwise ask for confirmation first. */
713 if (!window->fileChanged &&
714 /* Normal File */
715 ((!window->fileMissing && window->lastModTime > 0) ||
716 /* New File*/
717 (window->fileMissing && window->lastModTime == 0) ||
718 /* File deleted/modified externally, ignored by user. */
719 !GetPrefWarnFileMods()))
721 CloseWindow(window);
722 /* up-to-date windows don't have outstanding backup files to close */
723 } else
725 if (preResponse == PROMPT_SBC_DIALOG_RESPONSE)
727 response = DialogF(DF_WARN, window->shell, 3, "Save File",
728 "Save %s before closing?", "Yes", "No", "Cancel", window->filename);
729 } else
731 response = preResponse;
734 if (response == YES_SBC_DIALOG_RESPONSE)
736 /* Save */
737 stat = SaveWindow(window);
738 if (stat)
740 CloseWindow(window);
741 } else
743 return FALSE;
745 } else if (response == NO_SBC_DIALOG_RESPONSE)
747 /* Don't Save */
748 RemoveBackupFile(window);
749 CloseWindow(window);
750 } else /* 3 == Cancel */
752 return FALSE;
755 return TRUE;
758 int SaveWindow(WindowInfo *window)
760 int stat;
762 /* Try to ensure our information is up-to-date */
763 CheckForChangesToFile(window);
765 /* Return success if the file is normal & unchanged or is a
766 read-only file. */
767 if ( (!window->fileChanged && !window->fileMissing &&
768 window->lastModTime > 0) ||
769 IS_ANY_LOCKED_IGNORING_PERM(window->lockReasons))
770 return TRUE;
771 /* Prompt for a filename if this is an Untitled window */
772 if (!window->filenameSet)
773 return SaveWindowAs(window, NULL, False);
775 /* Check for external modifications and warn the user */
776 if (GetPrefWarnFileMods() && fileWasModifiedExternally(window))
778 stat = DialogF(DF_WARN, window->shell, 2, "Save File",
779 "%s has been modified by another program.\n\n"
780 "Continuing this operation will overwrite any external\n"
781 "modifications to the file since it was opened in NEdit,\n"
782 "and your work or someone else's may potentially be lost.\n\n"
783 "To preserve the modified file, cancel this operation and\n"
784 "use Save As... to save this file under a different name,\n"
785 "or Revert to Saved to revert to the modified version.",
786 "Continue", "Cancel", window->filename);
787 if (stat == 2)
789 /* Cancel and mark file as externally modified */
790 window->lastModTime = 0;
791 window->fileMissing = FALSE;
792 return FALSE;
796 #ifdef VMS
797 RemoveBackupFile(window);
798 stat = doSave(window);
799 #else
800 if (writeBckVersion(window))
801 return FALSE;
802 stat = doSave(window);
803 if (stat)
804 RemoveBackupFile(window);
805 #endif /*VMS*/
806 return stat;
809 int SaveWindowAs(WindowInfo *window, const char *newName, int addWrap)
811 int response, retVal, fileFormat;
812 char fullname[MAXPATHLEN], filename[MAXPATHLEN], pathname[MAXPATHLEN];
813 WindowInfo *otherWindow;
815 /* Get the new name for the file */
816 if (newName == NULL) {
817 response = PromptForNewFile(window, "Save File As", fullname,
818 &fileFormat, &addWrap);
819 if (response != GFN_OK)
820 return FALSE;
821 window->fileFormat = fileFormat;
822 } else
824 strcpy(fullname, newName);
827 if (1 == NormalizePathname(fullname))
829 return False;
832 /* Add newlines if requested */
833 if (addWrap)
834 addWrapNewlines(window);
836 if (ParseFilename(fullname, filename, pathname) != 0) {
837 return FALSE;
840 /* If the requested file is this file, just save it and return */
841 if (!strcmp(window->filename, filename) &&
842 !strcmp(window->path, pathname)) {
843 if (writeBckVersion(window))
844 return FALSE;
845 return doSave(window);
848 /* If the file is open in another window, make user close it. Note that
849 it is possible for user to close the window by hand while the dialog
850 is still up, because the dialog is not application modal, so after
851 doing the dialog, check again whether the window still exists. */
852 otherWindow = FindWindowWithFile(filename, pathname);
853 if (otherWindow != NULL)
855 response = DialogF(DF_WARN, window->shell, 2, "File open",
856 "%s is open in another NEdit window", "Cancel",
857 "Close Other Window", filename);
859 if (response == 1)
861 return FALSE;
863 if (otherWindow == FindWindowWithFile(filename, pathname))
865 if (!CloseFileAndWindow(otherWindow, PROMPT_SBC_DIALOG_RESPONSE))
867 return FALSE;
872 /* Destroy the file closed property for the original file */
873 DeleteFileClosedProperty(window);
875 /* Change the name of the file and save it under the new name */
876 RemoveBackupFile(window);
877 strcpy(window->filename, filename);
878 strcpy(window->path, pathname);
879 window->fileMode = 0;
880 window->fileUid = 0;
881 window->fileGid = 0;
882 CLEAR_ALL_LOCKS(window->lockReasons);
883 retVal = doSave(window);
884 UpdateWindowReadOnly(window);
885 RefreshTabState(window);
887 /* Add the name to the convenience menu of previously opened files */
888 AddToPrevOpenMenu(fullname);
890 /* If name has changed, language mode may have changed as well, unless
891 it's an Untitled window for which the user already set a language
892 mode; it's probably the right one. */
893 if (PLAIN_LANGUAGE_MODE == window->languageMode || window->filenameSet) {
894 DetermineLanguageMode(window, False);
896 window->filenameSet = True;
898 /* Update the stats line and window title with the new filename */
899 UpdateWindowTitle(window);
900 UpdateStatsLine(window);
902 SortTabBar(window);
903 return retVal;
906 static int doSave(WindowInfo *window)
908 char *fileString = NULL;
909 char fullname[MAXPATHLEN];
910 struct stat statbuf;
911 FILE *fp;
912 int fileLen, result;
914 /* Get the full name of the file */
915 strcpy(fullname, window->path);
916 strcat(fullname, window->filename);
918 /* Check for root and warn him if he wants to write to a file with
919 none of the write bits set. */
920 if ((0 == getuid())
921 && (0 == stat(fullname, &statbuf))
922 && !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH)))
924 result = DialogF(DF_WARN, window->shell, 2, "Writing Read-only File",
925 "File '%s' is marked as read-only.\n"
926 "Do you want to save anyway?",
927 "Save", "Cancel", window->filename);
928 if (1 != result)
930 return True;
934 #ifdef VMS
935 /* strip the version number from the file so VMS will begin a new one */
936 removeVersionNumber(fullname);
937 #endif
939 /* add a terminating newline if the file doesn't already have one for
940 Unix utilities which get confused otherwise
941 NOTE: this must be done _before_ we create/open the file, because the
942 (potential) buffer modification can trigger a check for file
943 changes. If the file is created for the first time, it has
944 zero size on disk, and the check would falsely conclude that the
945 file has changed on disk, and would pop up a warning dialog */
946 if (BufGetCharacter(window->buffer, window->buffer->length - 1) != '\n'
947 && window->buffer->length != 0
948 && GetPrefAppendLF())
950 BufInsert(window->buffer, window->buffer->length, "\n");
953 /* open the file */
954 #ifdef VMS
955 fp = fopen(fullname, "w", "rfm = stmlf");
956 #else
957 fp = fopen(fullname, "wb");
958 #endif /* VMS */
959 if (fp == NULL)
961 result = DialogF(DF_WARN, window->shell, 2, "Error saving File",
962 "Unable to save %s:\n%s\n\nSave as a new file?",
963 "Save As...", "Cancel",
964 window->filename, errorString());
966 if (result == 1)
968 return SaveWindowAs(window, NULL, 0);
970 return FALSE;
973 #ifdef VMS
974 /* get the complete name of the file including the new version number */
975 fgetname(fp, fullname);
976 #endif
978 /* get the text buffer contents and its length */
979 fileString = BufGetAll(window->buffer);
980 fileLen = window->buffer->length;
982 /* If null characters are substituted for, put them back */
983 BufUnsubstituteNullChars(fileString, window->buffer);
985 /* If the file is to be saved in DOS or Macintosh format, reconvert */
986 if (window->fileFormat == DOS_FILE_FORMAT)
988 if (!ConvertToDosFileString(&fileString, &fileLen))
990 DialogF(DF_ERR, window->shell, 1, "Out of Memory",
991 "Out of memory! Try\nsaving in Unix format", "OK");
992 return FALSE;
994 } else if (window->fileFormat == MAC_FILE_FORMAT)
996 ConvertToMacFileString(fileString, fileLen);
999 /* write to the file */
1000 #ifdef IBM_FWRITE_BUG
1001 write(fileno(fp), fileString, fileLen);
1002 #else
1003 fwrite(fileString, sizeof(char), fileLen, fp);
1004 #endif
1005 if (ferror(fp))
1007 DialogF(DF_ERR, window->shell, 1, "Error saving File",
1008 "%s not saved:\n%s", "OK", window->filename, errorString());
1009 fclose(fp);
1010 remove(fullname);
1011 XtFree(fileString);
1012 return FALSE;
1015 /* close the file */
1016 if (fclose(fp) != 0)
1018 DialogF(DF_ERR, window->shell, 1, "Error closing File",
1019 "Error closing file:\n%s", "OK", errorString());
1020 XtFree(fileString);
1021 return FALSE;
1024 /* free the text buffer copy returned from XmTextGetString */
1025 XtFree(fileString);
1027 #ifdef VMS
1028 /* reflect the fact that NEdit is now editing a new version of the file */
1029 ParseFilename(fullname, window->filename, window->path);
1030 #endif /*VMS*/
1032 /* success, file was written */
1033 SetWindowModified(window, FALSE);
1035 /* update the modification time */
1036 if (stat(fullname, &statbuf) == 0) {
1037 window->lastModTime = statbuf.st_mtime;
1038 window->fileMissing = FALSE;
1039 window->device = statbuf.st_dev;
1040 window->inode = statbuf.st_ino;
1041 } else {
1042 /* This needs to produce an error message -- the file can't be
1043 accessed! */
1044 window->lastModTime = 0;
1045 window->fileMissing = TRUE;
1046 window->device = 0;
1047 window->inode = 0;
1050 return TRUE;
1054 ** Create a backup file for the current window. The name for the backup file
1055 ** is generated using the name and path stored in the window and adding a
1056 ** tilde (~) on UNIX and underscore (_) on VMS to the beginning of the name.
1058 int WriteBackupFile(WindowInfo *window)
1060 char *fileString = NULL;
1061 char name[MAXPATHLEN];
1062 FILE *fp;
1063 int fd, fileLen;
1065 /* Generate a name for the autoSave file */
1066 backupFileName(window, name, sizeof(name));
1068 /* remove the old backup file.
1069 Well, this might fail - we'll notice later however. */
1070 remove(name);
1072 /* open the file, set more restrictive permissions (using default
1073 permissions was somewhat of a security hole, because permissions were
1074 independent of those of the original file being edited */
1075 #ifdef VMS
1076 if ((fp = fopen(name, "w", "rfm = stmlf")) == NULL)
1077 #else
1078 if ((fd = open(name, O_CREAT|O_EXCL|O_WRONLY, S_IRUSR | S_IWUSR)) < 0
1079 || (fp = fdopen(fd, "w")) == NULL)
1080 #endif /* VMS */
1082 DialogF(DF_WARN, window->shell, 1, "Error writing Backup",
1083 "Unable to save backup for %s:\n%s\n"
1084 "Automatic backup is now off", "OK", window->filename,
1085 errorString());
1086 window->autoSave = FALSE;
1087 SetToggleButtonState(window, window->autoSaveItem, FALSE, FALSE);
1088 return FALSE;
1091 /* Set VMS permissions */
1092 #ifdef VMS
1093 chmod(name, S_IRUSR | S_IWUSR);
1094 #endif
1096 /* get the text buffer contents and its length */
1097 fileString = BufGetAll(window->buffer);
1098 fileLen = window->buffer->length;
1100 /* If null characters are substituted for, put them back */
1101 BufUnsubstituteNullChars(fileString, window->buffer);
1103 /* add a terminating newline if the file doesn't already have one */
1104 if (fileLen != 0 && fileString[fileLen-1] != '\n')
1105 fileString[fileLen++] = '\n'; /* null terminator no longer needed */
1107 /* write out the file */
1108 #ifdef IBM_FWRITE_BUG
1109 write(fileno(fp), fileString, fileLen);
1110 #else
1111 fwrite(fileString, sizeof(char), fileLen, fp);
1112 #endif
1113 if (ferror(fp))
1115 DialogF(DF_ERR, window->shell, 1, "Error saving Backup",
1116 "Error while saving backup for %s:\n%s\n"
1117 "Automatic backup is now off", "OK", window->filename,
1118 errorString());
1119 fclose(fp);
1120 remove(name);
1121 XtFree(fileString);
1122 window->autoSave = FALSE;
1123 return FALSE;
1126 /* close the backup file */
1127 if (fclose(fp) != 0) {
1128 XtFree(fileString);
1129 return FALSE;
1132 /* Free the text buffer copy returned from XmTextGetString */
1133 XtFree(fileString);
1135 return TRUE;
1139 ** Remove the backup file associated with this window
1141 void RemoveBackupFile(WindowInfo *window)
1143 char name[MAXPATHLEN];
1145 /* Don't delete backup files when backups aren't activated. */
1146 if (window->autoSave == FALSE)
1147 return;
1149 backupFileName(window, name, sizeof(name));
1150 remove(name);
1154 ** Generate the name of the backup file for this window from the filename
1155 ** and path in the window data structure & write into name
1157 static void backupFileName(WindowInfo *window, char *name, size_t len)
1159 char bckname[MAXPATHLEN];
1160 #ifdef VMS
1161 if (window->filenameSet)
1162 sprintf(name, "%s_%s", window->path, window->filename);
1163 else
1164 sprintf(name, "%s_%s", "SYS$LOGIN:", window->filename);
1165 #else
1166 if (window->filenameSet)
1168 sprintf(name, "%s~%s", window->path, window->filename);
1169 } else
1171 strcpy(bckname, "~");
1172 strncat(bckname, window->filename, MAXPATHLEN - 1);
1173 PrependHome(bckname, name, len);
1175 #endif /*VMS*/
1179 ** If saveOldVersion is on, copies the existing version of the file to
1180 ** <filename>.bck in anticipation of a new version being saved. Returns
1181 ** True if backup fails and user requests that the new file not be written.
1183 static int writeBckVersion(WindowInfo *window)
1185 #ifndef VMS
1186 char fullname[MAXPATHLEN], bckname[MAXPATHLEN];
1187 struct stat statbuf;
1188 int in_fd, out_fd;
1189 char *io_buffer;
1190 #define IO_BUFFER_SIZE ((size_t)(1024*1024))
1192 /* Do only if version backups are turned on */
1193 if (!window->saveOldVersion) {
1194 return False;
1197 /* Get the full name of the file */
1198 strcpy(fullname, window->path);
1199 strcat(fullname, window->filename);
1201 /* Generate name for old version */
1202 if ((strlen(fullname) + 5) > (size_t) MAXPATHLEN) {
1203 return bckError(window, "file name too long", window->filename);
1205 sprintf(bckname, "%s.bck", fullname);
1207 /* Delete the old backup file */
1208 /* Errors are ignored; we'll notice them later. */
1209 remove(bckname);
1211 /* open the file being edited. If there are problems with the
1212 old file, don't bother the user, just skip the backup */
1213 in_fd = open(fullname, O_RDONLY);
1214 if (in_fd<0) {
1215 return FALSE;
1218 /* Get permissions of the file.
1219 We preserve the normal permissions but not ownership, extended
1220 attributes, et cetera. */
1221 if (fstat(in_fd, &statbuf) != 0) {
1222 return FALSE;
1225 /* open the destination file exclusive and with restrictive permissions. */
1226 out_fd = open(bckname, O_CREAT|O_EXCL|O_TRUNC|O_WRONLY, S_IRUSR | S_IWUSR);
1227 if (out_fd < 0) {
1228 return bckError(window, "Error open backup file", bckname);
1231 /* Set permissions on new file */
1232 if (fchmod(out_fd, statbuf.st_mode) != 0) {
1233 close(in_fd);
1234 close(out_fd);
1235 remove(bckname);
1236 return bckError(window, "fchmod() failed", bckname);
1239 /* Allocate I/O buffer */
1240 io_buffer = (char*) malloc(IO_BUFFER_SIZE);
1241 if (NULL == io_buffer) {
1242 close(in_fd);
1243 close(out_fd);
1244 remove(bckname);
1245 return bckError(window, "out of memory", bckname);
1248 /* copy loop */
1249 for(;;) {
1250 ssize_t bytes_read;
1251 ssize_t bytes_written;
1252 bytes_read = read(in_fd, io_buffer, IO_BUFFER_SIZE);
1254 if (bytes_read < 0) {
1255 close(in_fd);
1256 close(out_fd);
1257 remove(bckname);
1258 free(io_buffer);
1259 return bckError(window, "read() error", window->filename);
1262 if (0 == bytes_read) {
1263 break; /* EOF */
1266 /* write to the file */
1267 bytes_written = write(out_fd, io_buffer, (size_t) bytes_read);
1268 if (bytes_written != bytes_read) {
1269 close(in_fd);
1270 close(out_fd);
1271 remove(bckname);
1272 free(io_buffer);
1273 return bckError(window, errorString(), bckname);
1277 /* close the input and output files */
1278 close(in_fd);
1279 close(out_fd);
1281 free(io_buffer);
1283 #endif /* VMS */
1285 return FALSE;
1289 ** Error processing for writeBckVersion, gives the user option to cancel
1290 ** the subsequent save, or continue and optionally turn off versioning
1292 static int bckError(WindowInfo *window, const char *errString, const char *file)
1294 int resp;
1296 resp = DialogF(DF_ERR, window->shell, 3, "Error writing Backup",
1297 "Couldn't write .bck (last version) file.\n%s: %s", "Cancel Save",
1298 "Turn off Backups", "Continue", file, errString);
1299 if (resp == 1)
1300 return TRUE;
1301 if (resp == 2) {
1302 window->saveOldVersion = FALSE;
1303 #ifndef VMS
1304 SetToggleButtonState(window, window->saveLastItem, FALSE, FALSE);
1305 #endif
1307 return FALSE;
1310 void PrintWindow(WindowInfo *window, int selectedOnly)
1312 textBuffer *buf = window->buffer;
1313 selection *sel = &buf->primary;
1314 char *fileString = NULL;
1315 int fileLen;
1317 /* get the contents of the text buffer from the text area widget. Add
1318 wrapping newlines if necessary to make it match the displayed text */
1319 if (selectedOnly) {
1320 if (!sel->selected) {
1321 XBell(TheDisplay, 0);
1322 return;
1324 if (sel->rectangular) {
1325 fileString = BufGetSelectionText(buf);
1326 fileLen = strlen(fileString);
1327 } else
1328 fileString = TextGetWrapped(window->textArea, sel->start, sel->end,
1329 &fileLen);
1330 } else
1331 fileString = TextGetWrapped(window->textArea, 0, buf->length, &fileLen);
1333 /* If null characters are substituted for, put them back */
1334 BufUnsubstituteNullChars(fileString, buf);
1336 /* add a terminating newline if the file doesn't already have one */
1337 if (fileLen != 0 && fileString[fileLen-1] != '\n')
1338 fileString[fileLen++] = '\n'; /* null terminator no longer needed */
1340 /* Print the string */
1341 PrintString(fileString, fileLen, window->shell, window->filename);
1343 /* Free the text buffer copy returned from XmTextGetString */
1344 XtFree(fileString);
1348 ** Print a string (length is required). parent is the dialog parent, for
1349 ** error dialogs, and jobName is the print title.
1351 void PrintString(const char *string, int length, Widget parent, const char *jobName)
1353 char tmpFileName[L_tmpnam]; /* L_tmpnam defined in stdio.h */
1354 FILE *fp;
1355 int fd;
1357 /* Generate a temporary file name */
1358 /* If the glibc is used, the linker issues a warning at this point. This is
1359 very thoughtful of him, but does not apply to NEdit. The recommended
1360 replacement mkstemp(3) uses the same algorithm as NEdit, namely
1361 1. Create a filename
1362 2. Open the file with the O_CREAT|O_EXCL flags
1363 So all an attacker can do is a DoS on the print function. */
1364 tmpnam(tmpFileName);
1366 /* open the temporary file */
1367 #ifdef VMS
1368 if ((fp = fopen(tmpFileName, "w", "rfm = stmlf")) == NULL)
1369 #else
1370 if ((fd = open(tmpFileName, O_CREAT|O_EXCL|O_WRONLY, S_IRUSR | S_IWUSR)) < 0 || (fp = fdopen(fd, "w")) == NULL)
1371 #endif /* VMS */
1373 DialogF(DF_WARN, parent, 1, "Error while Printing",
1374 "Unable to write file for printing:\n%s", "OK",
1375 errorString());
1376 return;
1379 #ifdef VMS
1380 chmod(tmpFileName, S_IRUSR | S_IWUSR);
1381 #endif
1383 /* write to the file */
1384 #ifdef IBM_FWRITE_BUG
1385 write(fileno(fp), string, length);
1386 #else
1387 fwrite(string, sizeof(char), length, fp);
1388 #endif
1389 if (ferror(fp))
1391 DialogF(DF_ERR, parent, 1, "Error while Printing",
1392 "%s not printed:\n%s", "OK", jobName, errorString());
1393 fclose(fp); /* should call close(fd) in turn! */
1394 remove(tmpFileName);
1395 return;
1398 /* close the temporary file */
1399 if (fclose(fp) != 0)
1401 DialogF(DF_ERR, parent, 1, "Error while Printing",
1402 "Error closing temp. print file:\n%s", "OK",
1403 errorString());
1404 remove(tmpFileName);
1405 return;
1408 /* Print the temporary file, then delete it and return success */
1409 #ifdef VMS
1410 strcat(tmpFileName, ".");
1411 PrintFile(parent, tmpFileName, jobName, True);
1412 #else
1413 PrintFile(parent, tmpFileName, jobName);
1414 remove(tmpFileName);
1415 #endif /*VMS*/
1416 return;
1420 ** Wrapper for GetExistingFilename which uses the current window's path
1421 ** (if set) as the default directory.
1423 int PromptForExistingFile(WindowInfo *window, char *prompt, char *fullname)
1425 char *savedDefaultDir;
1426 int retVal;
1428 /* Temporarily set default directory to window->path, prompt for file,
1429 then, if the call was unsuccessful, restore the original default
1430 directory */
1431 savedDefaultDir = GetFileDialogDefaultDirectory();
1432 if (*window->path != '\0')
1433 SetFileDialogDefaultDirectory(window->path);
1434 retVal = GetExistingFilename(window->shell, prompt, fullname);
1435 if (retVal != GFN_OK)
1436 SetFileDialogDefaultDirectory(savedDefaultDir);
1438 XtFree(savedDefaultDir);
1440 return retVal;
1444 ** Wrapper for HandleCustomNewFileSB which uses the current window's path
1445 ** (if set) as the default directory, and asks about embedding newlines
1446 ** to make wrapping permanent.
1448 int PromptForNewFile(WindowInfo *window, char *prompt, char *fullname,
1449 int *fileFormat, int *addWrap)
1451 int n, retVal;
1452 Arg args[20];
1453 XmString s1, s2;
1454 Widget fileSB, wrapToggle;
1455 Widget formatForm, formatBtns, unixFormat, dosFormat, macFormat;
1456 char *savedDefaultDir;
1458 *fileFormat = window->fileFormat;
1460 /* Temporarily set default directory to window->path, prompt for file,
1461 then, if the call was unsuccessful, restore the original default
1462 directory */
1463 savedDefaultDir = GetFileDialogDefaultDirectory();
1464 if (*window->path != '\0')
1465 SetFileDialogDefaultDirectory(window->path);
1467 /* Present a file selection dialog with an added field for requesting
1468 long line wrapping to become permanent via inserted newlines */
1469 n = 0;
1470 XtSetArg(args[n],
1471 XmNselectionLabelString,
1472 s1 = XmStringCreateLocalized("New File Name:")); n++;
1473 XtSetArg(args[n], XmNdialogStyle, XmDIALOG_FULL_APPLICATION_MODAL); n++;
1474 XtSetArg(args[n],
1475 XmNdialogTitle,
1476 s2 = XmStringCreateSimple(prompt)); n++;
1477 fileSB = CreateFileSelectionDialog(window->shell,"FileSelect",args,n);
1478 XmStringFree(s1);
1479 XmStringFree(s2);
1480 formatForm = XtVaCreateManagedWidget("formatForm", xmFormWidgetClass,
1481 fileSB, NULL);
1482 formatBtns = XtVaCreateManagedWidget("formatBtns",
1483 xmRowColumnWidgetClass, formatForm,
1484 XmNradioBehavior, XmONE_OF_MANY,
1485 XmNorientation, XmHORIZONTAL,
1486 XmNpacking, XmPACK_TIGHT,
1487 XmNtopAttachment, XmATTACH_FORM,
1488 XmNleftAttachment, XmATTACH_FORM,
1489 NULL);
1490 XtVaCreateManagedWidget("formatBtns", xmLabelWidgetClass, formatBtns,
1491 XmNlabelString, s1=XmStringCreateSimple("Format:"), NULL);
1492 XmStringFree(s1);
1493 unixFormat = XtVaCreateManagedWidget("unixFormat",
1494 xmToggleButtonWidgetClass, formatBtns,
1495 XmNlabelString, s1 = XmStringCreateSimple("Unix"),
1496 XmNset, *fileFormat == UNIX_FILE_FORMAT,
1497 XmNuserData, (XtPointer)UNIX_FILE_FORMAT,
1498 XmNmarginHeight, 0,
1499 XmNalignment, XmALIGNMENT_BEGINNING,
1500 XmNmnemonic, 'U',
1501 NULL);
1502 XmStringFree(s1);
1503 XtAddCallback(unixFormat, XmNvalueChangedCallback, setFormatCB,
1504 fileFormat);
1505 dosFormat = XtVaCreateManagedWidget("dosFormat",
1506 xmToggleButtonWidgetClass, formatBtns,
1507 XmNlabelString, s1 = XmStringCreateSimple("DOS"),
1508 XmNset, *fileFormat == DOS_FILE_FORMAT,
1509 XmNuserData, (XtPointer)DOS_FILE_FORMAT,
1510 XmNmarginHeight, 0,
1511 XmNalignment, XmALIGNMENT_BEGINNING,
1512 XmNmnemonic, 'O',
1513 NULL);
1514 XmStringFree(s1);
1515 XtAddCallback(dosFormat, XmNvalueChangedCallback, setFormatCB,
1516 fileFormat);
1517 macFormat = XtVaCreateManagedWidget("macFormat",
1518 xmToggleButtonWidgetClass, formatBtns,
1519 XmNlabelString, s1 = XmStringCreateSimple("Macintosh"),
1520 XmNset, *fileFormat == MAC_FILE_FORMAT,
1521 XmNuserData, (XtPointer)MAC_FILE_FORMAT,
1522 XmNmarginHeight, 0,
1523 XmNalignment, XmALIGNMENT_BEGINNING,
1524 XmNmnemonic, 'M',
1525 NULL);
1526 XmStringFree(s1);
1527 XtAddCallback(macFormat, XmNvalueChangedCallback, setFormatCB,
1528 fileFormat);
1529 if (window->wrapMode == CONTINUOUS_WRAP) {
1530 wrapToggle = XtVaCreateManagedWidget("addWrap",
1531 xmToggleButtonWidgetClass, formatForm,
1532 XmNlabelString, s1 = XmStringCreateSimple("Add line breaks where wrapped"),
1533 XmNalignment, XmALIGNMENT_BEGINNING,
1534 XmNmnemonic, 'A',
1535 XmNtopAttachment, XmATTACH_WIDGET,
1536 XmNtopWidget, formatBtns,
1537 XmNleftAttachment, XmATTACH_FORM,
1538 NULL);
1539 XtAddCallback(wrapToggle, XmNvalueChangedCallback, addWrapCB,
1540 addWrap);
1541 XmStringFree(s1);
1543 *addWrap = False;
1544 XtVaSetValues(XmFileSelectionBoxGetChild(fileSB, XmDIALOG_FILTER_LABEL),
1545 XmNmnemonic, 'l',
1546 XmNuserData, XmFileSelectionBoxGetChild(fileSB, XmDIALOG_FILTER_TEXT),
1547 NULL);
1548 XtVaSetValues(XmFileSelectionBoxGetChild(fileSB, XmDIALOG_DIR_LIST_LABEL),
1549 XmNmnemonic, 'D',
1550 XmNuserData, XmFileSelectionBoxGetChild(fileSB, XmDIALOG_DIR_LIST),
1551 NULL);
1552 XtVaSetValues(XmFileSelectionBoxGetChild(fileSB, XmDIALOG_LIST_LABEL),
1553 XmNmnemonic, 'F',
1554 XmNuserData, XmFileSelectionBoxGetChild(fileSB, XmDIALOG_LIST),
1555 NULL);
1556 XtVaSetValues(XmFileSelectionBoxGetChild(fileSB, XmDIALOG_SELECTION_LABEL),
1557 XmNmnemonic, prompt[strspn(prompt, "lFD")],
1558 XmNuserData, XmFileSelectionBoxGetChild(fileSB, XmDIALOG_TEXT),
1559 NULL);
1560 AddDialogMnemonicHandler(fileSB, FALSE);
1561 RemapDeleteKey(XmFileSelectionBoxGetChild(fileSB, XmDIALOG_FILTER_TEXT));
1562 RemapDeleteKey(XmFileSelectionBoxGetChild(fileSB, XmDIALOG_TEXT));
1563 retVal = HandleCustomNewFileSB(fileSB, fullname,
1564 window->filenameSet ? window->filename : NULL);
1566 if (retVal != GFN_OK)
1567 SetFileDialogDefaultDirectory(savedDefaultDir);
1569 XtFree(savedDefaultDir);
1571 return retVal;
1575 ** Find a name for an untitled file, unique in the name space of in the opened
1576 ** files in this session, i.e. Untitled or Untitled_nn, and write it into
1577 ** the string "name".
1579 void UniqueUntitledName(char *name)
1581 WindowInfo *w;
1582 int i;
1584 for (i=0; i<INT_MAX; i++) {
1585 if (i == 0)
1586 sprintf(name, "Untitled");
1587 else
1588 sprintf(name, "Untitled_%d", i);
1589 for (w=WindowList; w!=NULL; w=w->next)
1590 if (!strcmp(w->filename, name))
1591 break;
1592 if (w == NULL)
1593 break;
1598 ** Callback that guards us from trying to access a window after it has
1599 ** been destroyed while a modal dialog is up.
1601 static void modifiedWindowDestroyedCB(Widget w, XtPointer clientData,
1602 XtPointer callData)
1604 *(Bool*)clientData = TRUE;
1608 ** Check if the file in the window was changed by an external source.
1609 ** and put up a warning dialog if it has.
1611 void CheckForChangesToFile(WindowInfo *window)
1613 static WindowInfo* lastCheckWindow = NULL;
1614 static Time lastCheckTime = 0;
1615 char fullname[MAXPATHLEN];
1616 struct stat statbuf;
1617 Time timestamp;
1618 FILE *fp;
1619 int resp, silent = 0;
1620 XWindowAttributes winAttr;
1621 Boolean windowIsDestroyed = False;
1623 if(!window->filenameSet)
1624 return;
1626 /* If last check was very recent, don't impact performance */
1627 timestamp = XtLastTimestampProcessed(XtDisplay(window->shell));
1628 if (window == lastCheckWindow &&
1629 timestamp - lastCheckTime < MOD_CHECK_INTERVAL)
1630 return;
1631 lastCheckWindow = window;
1632 lastCheckTime = timestamp;
1634 /* Update the status, but don't pop up a dialog if we're called
1635 from a place where the window might be iconic (e.g., from the
1636 replace dialog) or on another desktop.
1638 This works, but I bet it costs a round-trip to the server.
1639 Might be better to capture MapNotify/Unmap events instead.
1641 For tabs that are not on top, we don't want the dialog either,
1642 and we don't even need to contact the server to find out. By
1643 performing this check first, we avoid a server round-trip for
1644 most files in practice. */
1645 if (!IsTopDocument(window))
1646 silent = 1;
1647 else {
1648 XGetWindowAttributes(XtDisplay(window->shell),
1649 XtWindow(window->shell),
1650 &winAttr);
1652 if (winAttr.map_state != IsViewable)
1653 silent = 1;
1656 /* Get the file mode and modification time */
1657 strcpy(fullname, window->path);
1658 strcat(fullname, window->filename);
1659 if (stat(fullname, &statbuf) != 0) {
1660 /* Return if we've already warned the user or we can't warn him now */
1661 if (window->fileMissing || silent) {
1662 return;
1665 /* Can't stat the file -- maybe it's been deleted.
1666 The filename is now invalid */
1667 window->fileMissing = TRUE;
1668 window->lastModTime = 1;
1669 window->device = 0;
1670 window->inode = 0;
1672 /* Warn the user, if they like to be warned (Maybe this should be its
1673 own preference setting: GetPrefWarnFileDeleted()) */
1674 if (GetPrefWarnFileMods()) {
1675 char* title;
1676 char* body;
1678 /* See note below about pop-up timing and XUngrabPointer */
1679 XUngrabPointer(XtDisplay(window->shell), timestamp);
1681 /* If the window (and the dialog) are destroyed while the dialog
1682 is up (typically closed via the window manager), we should
1683 avoid accessing the window afterwards. */
1684 XtAddCallback(window->shell, XmNdestroyCallback,
1685 modifiedWindowDestroyedCB, &windowIsDestroyed);
1687 /* Set title, message body and button to match stat()'s error. */
1688 switch (errno) {
1689 case ENOENT:
1690 /* A component of the path file_name does not exist. */
1691 title = "File not Found";
1692 body = "File '%s' (or directory in its path)\n"
1693 "no longer exists.\n"
1694 "Another program may have deleted or moved it.";
1695 resp = DialogF(DF_ERR, window->shell, 2, title, body,
1696 "Save", "Cancel", window->filename);
1697 break;
1698 case EACCES:
1699 /* Search permission denied for a path component. We add
1700 one to the response because Re-Save wouldn't really
1701 make sense here. */
1702 title = "Permission Denied";
1703 body = "You no longer have access to file '%s'.\n"
1704 "Another program may have changed the permissions of\n"
1705 "one of its parent directories.";
1706 resp = 1 + DialogF(DF_ERR, window->shell, 1, title, body,
1707 "Cancel", window->filename);
1708 break;
1709 default:
1710 /* Everything else. This hints at an internal error (eg.
1711 ENOTDIR) or at some bad state at the host. */
1712 title = "File not Accessible";
1713 body = "Error while checking the status of file '%s':\n"
1714 " '%s'\n"
1715 "Please make sure that no data is lost before closing\n"
1716 "this window.";
1717 resp = DialogF(DF_ERR, window->shell, 2, title, body,
1718 "Save", "Cancel", window->filename,
1719 errorString());
1720 break;
1723 if (!windowIsDestroyed) {
1724 XtRemoveCallback(window->shell, XmNdestroyCallback,
1725 modifiedWindowDestroyedCB, &windowIsDestroyed);
1728 switch (resp) {
1729 case 1:
1730 SaveWindow(window);
1731 break;
1732 /* Good idea, but this leads to frequent crashes, see
1733 SF#1578869. Reinsert this if circumstances change by
1734 uncommenting this part and inserting a "Close" button
1735 before each Cancel button above.
1736 case 2:
1737 CloseWindow(window);
1738 return;
1743 /* A missing or (re-)saved file can't be read-only. */
1744 /* TODO: A document without a file can be locked though. */
1745 /* Make sure that the window was not destroyed behind our back! */
1746 if (!windowIsDestroyed) {
1747 SET_PERM_LOCKED(window->lockReasons, False);
1748 UpdateWindowTitle(window);
1749 UpdateWindowReadOnly(window);
1751 return;
1754 /* Check that the file's read-only status is still correct (but
1755 only if the file can still be opened successfully in read mode) */
1756 if (window->fileMode != statbuf.st_mode ||
1757 window->fileUid != statbuf.st_uid ||
1758 window->fileGid != statbuf.st_gid) {
1759 window->fileMode = statbuf.st_mode;
1760 window->fileUid = statbuf.st_uid;
1761 window->fileGid = statbuf.st_gid;
1762 if ((fp = fopen(fullname, "r")) != NULL) {
1763 int readOnly;
1764 fclose(fp);
1765 #ifndef DONT_USE_ACCESS
1766 readOnly = access(fullname, W_OK) != 0;
1767 #else
1768 if (((fp = fopen(fullname, "r+")) != NULL)) {
1769 readOnly = FALSE;
1770 fclose(fp);
1771 } else
1772 readOnly = TRUE;
1773 #endif
1774 if (IS_PERM_LOCKED(window->lockReasons) != readOnly) {
1775 SET_PERM_LOCKED(window->lockReasons, readOnly);
1776 UpdateWindowTitle(window);
1777 UpdateWindowReadOnly(window);
1782 /* Warn the user if the file has been modified, unless checking is
1783 turned off or the user has already been warned. Popping up a dialog
1784 from a focus callback (which is how this routine is usually called)
1785 seems to catch Motif off guard, and if the timing is just right, the
1786 dialog can be left with a still active pointer grab from a Motif menu
1787 which is still in the process of popping down. The workaround, below,
1788 of calling XUngrabPointer is inelegant but seems to fix the problem. */
1789 if (!silent &&
1790 ((window->lastModTime != 0 &&
1791 window->lastModTime != statbuf.st_mtime) ||
1792 window->fileMissing) ){
1793 window->lastModTime = 0; /* Inhibit further warnings */
1794 window->fileMissing = FALSE;
1795 if (!GetPrefWarnFileMods())
1796 return;
1797 if (GetPrefWarnRealFileMods() &&
1798 !cmpWinAgainstFile(window, fullname)) {
1799 /* Contents hasn't changed. Update the modification time. */
1800 window->lastModTime = statbuf.st_mtime;
1801 return;
1803 XUngrabPointer(XtDisplay(window->shell), timestamp);
1804 if (window->fileChanged)
1805 resp = DialogF(DF_WARN, window->shell, 2,
1806 "File modified externally",
1807 "%s has been modified by another program. Reload?\n\n"
1808 "WARNING: Reloading will discard changes made in this\n"
1809 "editing session!", "Reload", "Cancel", window->filename);
1810 else
1811 resp = DialogF(DF_WARN, window->shell, 2,
1812 "File modified externally",
1813 "%s has been modified by another\nprogram. Reload?",
1814 "Reload", "Cancel", window->filename);
1815 if (resp == 1)
1816 RevertToSaved(window);
1821 ** Return true if the file displayed in window has been modified externally
1822 ** to nedit. This should return FALSE if the file has been deleted or is
1823 ** unavailable.
1825 static int fileWasModifiedExternally(WindowInfo *window)
1827 char fullname[MAXPATHLEN];
1828 struct stat statbuf;
1830 if(!window->filenameSet)
1831 return FALSE;
1832 /* if (window->lastModTime == 0)
1833 return FALSE; */
1834 strcpy(fullname, window->path);
1835 strcat(fullname, window->filename);
1836 if (stat(fullname, &statbuf) != 0)
1837 return FALSE;
1838 if (window->lastModTime == statbuf.st_mtime)
1839 return FALSE;
1840 if (GetPrefWarnRealFileMods() &&
1841 !cmpWinAgainstFile(window, fullname)) {
1842 return FALSE;
1844 return TRUE;
1848 ** Check the read-only or locked status of the window and beep and return
1849 ** false if the window should not be written in.
1851 int CheckReadOnly(WindowInfo *window)
1853 if (IS_ANY_LOCKED(window->lockReasons)) {
1854 XBell(TheDisplay, 0);
1855 return True;
1857 return False;
1861 ** Wrapper for strerror so all the calls don't have to be ifdef'd for VMS.
1863 static const char *errorString(void)
1865 #ifdef VMS
1866 return strerror(errno, vaxc$errno);
1867 #else
1868 return strerror(errno);
1869 #endif
1872 #ifdef VMS
1874 ** Removing the VMS version number from a file name (if has one).
1876 void removeVersionNumber(char *fileName)
1878 char *versionStart;
1880 versionStart = strrchr(fileName, ';');
1881 if (versionStart != NULL)
1882 *versionStart = '\0';
1884 #endif /*VMS*/
1887 ** Callback procedure for File Format toggle buttons. Format is stored
1888 ** in userData field of widget button
1890 static void setFormatCB(Widget w, XtPointer clientData, XtPointer callData)
1892 if (XmToggleButtonGetState(w)) {
1893 XtPointer userData;
1894 XtVaGetValues(w, XmNuserData, &userData, NULL);
1895 *(int*) clientData = (int) userData;
1900 ** Callback procedure for toggle button requesting newlines to be inserted
1901 ** to emulate continuous wrapping.
1903 static void addWrapCB(Widget w, XtPointer clientData, XtPointer callData)
1905 int resp;
1906 int *addWrap = (int *)clientData;
1908 if (XmToggleButtonGetState(w))
1910 resp = DialogF(DF_WARN, w, 2, "Add Wrap",
1911 "This operation adds permanent line breaks to\n"
1912 "match the automatic wrapping done by the\n"
1913 "Continuous Wrap mode Preferences Option.\n\n"
1914 "*** This Option is Irreversable ***\n\n"
1915 "Once newlines are inserted, continuous wrapping\n"
1916 "will no longer work automatically on these lines", "OK",
1917 "Cancel");
1918 if (resp == 2)
1920 XmToggleButtonSetState(w, False, False);
1921 *addWrap = False;
1922 } else
1924 *addWrap = True;
1926 } else
1928 *addWrap = False;
1933 ** Change a window created in NEdit's continuous wrap mode to the more
1934 ** conventional Unix format of embedded newlines. Indicate to the user
1935 ** by turning off Continuous Wrap mode.
1937 static void addWrapNewlines(WindowInfo *window)
1939 int fileLen, i, insertPositions[MAX_PANES], topLines[MAX_PANES];
1940 int horizOffset;
1941 Widget text;
1942 char *fileString;
1944 /* save the insert and scroll positions of each pane */
1945 for (i=0; i<=window->nPanes; i++) {
1946 text = i==0 ? window->textArea : window->textPanes[i-1];
1947 insertPositions[i] = TextGetCursorPos(text);
1948 TextGetScroll(text, &topLines[i], &horizOffset);
1951 /* Modify the buffer to add wrapping */
1952 fileString = TextGetWrapped(window->textArea, 0,
1953 window->buffer->length, &fileLen);
1954 BufSetAll(window->buffer, fileString);
1955 XtFree(fileString);
1957 /* restore the insert and scroll positions of each pane */
1958 for (i=0; i<=window->nPanes; i++) {
1959 text = i==0 ? window->textArea : window->textPanes[i-1];
1960 TextSetCursorPos(text, insertPositions[i]);
1961 TextSetScroll(text, topLines[i], 0);
1964 /* Show the user that something has happened by turning off
1965 Continuous Wrap mode */
1966 SetToggleButtonState(window, window->continuousWrapItem, False, True);
1970 * Number of bytes read at once by cmpWinAgainstFile
1972 #define PREFERRED_CMPBUF_LEN 32768
1975 * Check if the contens of the textBuffer *buf is equal
1976 * the contens of the file named fileName. The format of
1977 * the file (UNIX/DOS/MAC) is handled properly.
1979 * Return values
1980 * 0: no difference found
1981 * !0: difference found or could not compare contents.
1983 static int cmpWinAgainstFile(WindowInfo *window, const char *fileName)
1985 char fileString[PREFERRED_CMPBUF_LEN + 2];
1986 struct stat statbuf;
1987 int fileLen, restLen, nRead, bufPos, rv, offset, filePos;
1988 char pendingCR = 0;
1989 int fileFormat = window->fileFormat;
1990 char message[MAXPATHLEN+50];
1991 textBuffer *buf = window->buffer;
1992 FILE *fp;
1994 fp = fopen(fileName, "r");
1995 if (!fp)
1996 return (1);
1997 if (fstat(fileno(fp), &statbuf) != 0) {
1998 fclose(fp);
1999 return (1);
2002 fileLen = statbuf.st_size;
2003 /* For DOS files, we can't simply check the length */
2004 if (fileFormat != DOS_FILE_FORMAT) {
2005 if (fileLen != buf->length) {
2006 fclose(fp);
2007 return (1);
2009 } else {
2010 /* If a DOS file is smaller on disk, it's certainly different */
2011 if (fileLen < buf->length) {
2012 fclose(fp);
2013 return (1);
2017 /* For large files, the comparison can take a while. If it takes too long,
2018 the user should be given a clue about what is happening. */
2019 sprintf(message, "Comparing externally modified %s ...", window->filename);
2020 restLen = min(PREFERRED_CMPBUF_LEN, fileLen);
2021 bufPos = 0;
2022 filePos = 0;
2023 while (restLen > 0) {
2024 AllWindowsBusy(message);
2025 if (pendingCR) {
2026 fileString[0] = pendingCR;
2027 offset = 1;
2028 } else {
2029 offset = 0;
2032 nRead = fread(fileString+offset, sizeof(char), restLen, fp);
2033 if (nRead != restLen) {
2034 fclose(fp);
2035 AllWindowsUnbusy();
2036 return (1);
2038 filePos += nRead;
2040 nRead += offset;
2042 if (fileFormat == MAC_FILE_FORMAT)
2043 ConvertFromMacFileString(fileString, nRead);
2044 else if (fileFormat == DOS_FILE_FORMAT)
2045 ConvertFromDosFileString(fileString, &nRead, &pendingCR);
2047 /* Beware of 0 chars ! */
2048 BufSubstituteNullChars(fileString, nRead, buf);
2049 rv = BufCmp(buf, bufPos, nRead, fileString);
2050 if (rv) {
2051 fclose(fp);
2052 AllWindowsUnbusy();
2053 return (rv);
2055 bufPos += nRead;
2056 restLen = min(fileLen - filePos, PREFERRED_CMPBUF_LEN);
2058 AllWindowsUnbusy();
2059 fclose(fp);
2060 if (pendingCR) {
2061 rv = BufCmp(buf, bufPos, 1, &pendingCR);
2062 if (rv) {
2063 return (rv);
2065 bufPos += 1;
2067 if (bufPos != buf->length) {
2068 return (1);
2070 return (0);
2074 ** Force ShowLineNumbers() to re-evaluate line counts for the window if line
2075 ** counts are required.
2077 static void forceShowLineNumbers(WindowInfo *window)
2079 Boolean showLineNum = window->showLineNumbers;
2080 if (showLineNum) {
2081 window->showLineNumbers = False;
2082 ShowLineNumbers(window, showLineNum);
2086 static int min(int i1, int i2)
2088 return i1 <= i2 ? i1 : i2;