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