    Allow NormalizePathname() and ParseFilename() to use windows path

    Various operations require the extension of an incomplete file specification
    to a full, absolute path. Before this patch, this completion uses the
    current directory of the NEdit process as the base for relative file lookup.
    However, this works against NEdit's file dialogs and shell processing which
    start off in the directory of the current document, which may well not be
    the same as the process' directory. Using "File > Open Selected" may not, in
    this situation, find the file you would expect.

    This patch alters NormalizePathname() and ParseFilename(), the utility
    functions that perform the expansion so that the base for relative file
    specifications can be passed, then changes all relevant calls to supply the
    current window's file path value. It includes the nmReadWriteRelToWin.diff
    adjustments to macro file functions read_file(), write_file() and
    append_file() for this same behaviour, and adds a new one, full_file_name(),
    which provides the macro writer with a way of obtaining the relative file
    name from a relative file path.

diff -ur nedit_official nedit_mod
diff -ur nedit_official/source/file.c nedit_mod/source/file.c
--- nedit_official/source/file.c	2008-01-05 14:48:18.000000000 +0100
+++ nedit_mod/source/file.c	2008-01-19 00:43:29.000000000 +0100
@@ -824,7 +824,7 @@
         strcpy(fullname, newName);
     }
 
-    if (1 == NormalizePathname(fullname))
+    if (1 == NormalizePathname(fullname, window->path))
     {
         return False;
     }
@@ -833,7 +833,7 @@
     if (addWrap)
     	addWrapNewlines(window);
     
-    if (ParseFilename(fullname, filename, pathname) != 0) {
+    if (ParseFilename(fullname, filename, pathname, window->path) != 0) {
        return FALSE;
     }
 
@@ -1026,7 +1026,7 @@
     
 #ifdef VMS
     /* reflect the fact that NEdit is now editing a new version of the file */
-    ParseFilename(fullname, window->filename, window->path);
+    ParseFilename(fullname, window->filename, window->path, NULL);
 #endif /*VMS*/
 
     /* success, file was written */
diff -ur nedit_official/source/macro.c nedit_mod/source/macro.c
--- nedit_official/source/macro.c	2007-10-04 18:04:25.000000000 +0200
+++ nedit_mod/source/macro.c	2008-01-19 00:49:52.000000000 +0100
@@ -179,6 +179,8 @@
     	DataValue *result, char **errMsg);
 static int replaceSubstringMS(WindowInfo *window, DataValue *argList, int nArgs,
     	DataValue *result, char **errMsg);
+static int fullFileNameMS(WindowInfo *window, DataValue *argList, int nArgs,
+        DataValue *result, char **errMsg);
 static int readFileMS(WindowInfo *window, DataValue *argList, int nArgs,
     	DataValue *result, char **errMsg);
 static int writeFileMS(WindowInfo *window, DataValue *argList, int nArgs,
@@ -410,7 +412,8 @@
 static BuiltInSubr MacroSubrs[] = {lengthMS, getRangeMS, tPrintMS,
         dialogMS, stringDialogMS, replaceRangeMS, replaceSelectionMS,
         setCursorPosMS, getCharacterMS, minMS, maxMS, searchMS,
-        searchStringMS, substringMS, replaceSubstringMS, readFileMS,
+        searchStringMS, substringMS, replaceSubstringMS, fullFileNameMS,
+        readFileMS,
         writeFileMS, appendFileMS, beepMS, getSelectionMS, validNumberMS,
         replaceInStringMS, selectMS, selectRectangleMS, focusWindowMS,
         shellCmdMS, stringToClipboardMS, clipboardToStringMS, toupperMS,
@@ -429,7 +432,8 @@
 static const char *MacroSubrNames[N_MACRO_SUBRS] = {"length", "get_range", "t_print",
         "dialog", "string_dialog", "replace_range", "replace_selection",
         "set_cursor_pos", "get_character", "min", "max", "search",
-        "search_string", "substring", "replace_substring", "read_file",
+        "search_string", "substring", "replace_substring", "full_file_name",
+        "read_file",
         "write_file", "append_file", "beep", "get_selection", "valid_number",
         "replace_in_string", "select", "select_rectangle", "focus_window",
         "shell_command", "string_to_clipboard", "clipboard_to_string",
@@ -1887,7 +1891,7 @@
         if (w == NULL) {
             strncpy(normalizedString, string, MAXPATHLEN);
             normalizedString[MAXPATHLEN-1] = '\0';
-            if (1 == NormalizePathname(normalizedString)) {
+            if (1 == NormalizePathname(normalizedString, window->path)) {
                 /*  Something is broken with the input pathname. */
                 *errMsg = "Pathname too long in focus_window()";
                 return False;
@@ -2323,6 +2327,108 @@
     return True;
 }
 
+/*
+** Resolve the partial file path in name with respect to path.
+** (We don't use NormalizePathname() since this modifies its path in place.)
+*/
+static char *convFilePathToAbsolute(const char *path, char *name)
+{
+    static char *nameText = NULL, *ptr;
+    static size_t nameTextLen = 0;
+    size_t namelen, pathlen, len;
+    Bool isRelative = False;
+    size_t needSlash = 0;
+
+    if (!path || !path[0] || strcmp(path, ".") == 0)
+        path = GetCurrentDir();
+
+#ifdef VMS
+    /* If path name is relative, make it refer to current window's directory;
+       absolute paths start with a logical name ([\w$]+) followed by a colon,
+       or with a non-relative path in brackets (not starting with . or -)
+     */
+    isRelative = ((strchr(name, ':') == NULL) && (strlen(name) > 1) &&
+                  !((name[0] == '[') && (name[1] != '-') &&
+                   (name[1] != '.')));
+#else
+    /* UNIX-like:
+       Any path starting without a "/" is considered relative; if the first
+       character is "~", we really need the user's home directory */
+    if (name[0] == '~' && name[1] == '/') {
+        path = GetHomeDir();
+        for (++name; *name == '/'; ++name)
+            continue;   /* skip slash(es) following initial tilde */
+        isRelative = True;
+    }
+    else
+        isRelative = (name[0] != '/');
+#endif
+
+    if (!isRelative)
+        return name;
+
+    namelen = strlen(name);
+    pathlen = strlen(path);
+
+#ifndef VMS
+    needSlash = (path && pathlen && path[pathlen - 1] != '/') ? 1 : 0;
+#endif
+
+    /* make sure the buffer's big enough */
+    len = namelen + pathlen + needSlash;
+
+    if (nameTextLen < len) {
+        ptr = realloc(nameText, len + 1);
+        if (!ptr)
+            return NULL;
+        nameText = ptr;
+        nameTextLen = len;
+    }
+
+    /* copy in pieces */
+    strcpy(nameText, path);
+    if (needSlash)
+        nameText[pathlen] = '/';
+    strcpy(nameText + pathlen + needSlash, name);
+
+    CompressPathname(nameText);
+
+    return nameText;
+}
+
+/*
+** Built-in macro subroutine for expanding a possibly partial file specification
+** to a full one, using the current window's directory as a base for relative
+** path specifications. It does not check for file/path validity.
+*/
+static int fullFileNameMS(WindowInfo *window, DataValue *argList, int nArgs,
+        DataValue *result, char **errMsg)
+{
+    char stringStorage[TYPE_INT_STR_SIZE(int)], *name;
+    size_t len;
+
+    /* Validate arguments and convert to int */
+    if (nArgs != 1)
+        return wrongNArgsErr(errMsg);
+    if (!readStringArg(argList[0], &name, stringStorage, errMsg))
+        return False;
+
+    name = convFilePathToAbsolute(window->path, name);
+    len = strlen(name);
+
+    if (name) {
+        result->tag = STRING_TAG;
+        AllocNString(&result->val.str, len + 1);
+        strcpy(result->val.str.rep, name);
+    }
+    else {
+        result->tag = STRING_TAG;
+        result->val.str.rep = PERM_ALLOC_STR("");
+        result->val.str.len = 0;
+    }
+
+    return True;
+}
 
 /*
 ** Built-in macro subroutine for reading the contents of a text file into
@@ -2343,7 +2449,9 @@
     	return wrongNArgsErr(errMsg);
     if (!readStringArg(argList[0], &name, stringStorage, errMsg))
     	return False;
-    
+
+    name = convFilePathToAbsolute(window->path, name);
+
     /* Read the whole file into an allocated string */
     if ((fp = fopen(name, "r")) == NULL)
     	goto errorNoClose;
@@ -2422,7 +2530,9 @@
     	return False;
     if (!readStringArg(argList[1], &name, stringStorage[0], errMsg))
     	return False;
-    
+
+    name = convFilePathToAbsolute(window->path, name);
+
     /* open the file */
     if ((fp = fopen(name, append ? "a" : "w")) == NULL) {
 	result->tag = INT_TAG;
diff -ur nedit_official/source/menu.c nedit_mod/source/menu.c
--- nedit_official/source/menu.c	2008-01-04 23:11:03.000000000 +0100
+++ nedit_mod/source/menu.c	2008-01-19 00:43:29.000000000 +0100
@@ -2788,7 +2788,7 @@
     	fprintf(stderr, "nedit: open action requires file argument\n");
     	return;
     }
-    if (0 != ParseFilename(args[0], filename, pathname)
+    if (0 != ParseFilename(args[0], filename, pathname, window->path)
             || strlen(filename) + strlen(pathname) > MAXPATHLEN - 1) {
         fprintf(stderr, "nedit: invalid file name for open action: %s\n",
                 args[0]);
diff -ur nedit_official/source/nc.c nedit_mod/source/nc.c
--- nedit_official/source/nc.c	2007-11-29 23:18:48.000000000 +0100
+++ nedit_mod/source/nc.c	2008-01-19 00:43:29.000000000 +0100
@@ -714,7 +714,7 @@
 	    	XtFree(commandString);
 	    	commandString = newCommandString;
 	    	outPtr = newCommandString + oldLength;
-	    	if (ParseFilename(nameList[j], name, path) != 0) {
+                if (ParseFilename(nameList[j], name, path, NULL) != 0) {
 	           /* An Error, most likely too long paths/strings given */
 	           commandLine->serverRequest = NULL;
 	           return;
@@ -750,7 +750,7 @@
 	    if (nameList != NULL)
 	    	free(nameList);
 #else
-    	    if (ParseFilename(argv[i], name, path) != 0) {
+            if (ParseFilename(argv[i], name, path, NULL) != 0) {
 	       /* An Error, most likely too long paths/strings given */
 	       commandLine->serverRequest = NULL;
 	       return;
diff -ur nedit_official/source/nedit.c nedit_mod/source/nedit.c
--- nedit_official/source/nedit.c	2007-11-29 23:18:09.000000000 +0100
+++ nedit_mod/source/nedit.c	2008-01-19 00:43:29.000000000 +0100
@@ -650,7 +650,7 @@
 	    numFiles = VMSFileScan(argv[i], &nameList, NULL, INCLUDE_FNF);
 	    /* for each expanded file name do: */
 	    for (j = 0; j < numFiles; ++j) {
-	    	if (ParseFilename(nameList[j], filename, pathname) == 0) {
-		    /* determine if file is to be openned in new tab, by
+                if (ParseFilename(nameList[j], filename, pathname, NULL) == 0) {
+                    /* determine if file is to be opened in new tab, by
 		       factoring the options -group, -tabbed & -untabbed */
     		    if (group == 2) {
@@ -709,7 +709,7 @@
 	    if (nameList != NULL)
 	    	free(nameList);
 #else
-	    if (ParseFilename(argv[i], filename, pathname) == 0 ) {
-		/* determine if file is to be openned in new tab, by
+            if (ParseFilename(argv[i], filename, pathname, NULL) == 0) {
+                /* determine if file is to be opened in new tab, by
 		   factoring the options -group, -tabbed & -untabbed */
     		if (group == 2) {
diff -ur nedit_official/source/selection.c nedit_mod/source/selection.c
--- nedit_official/source/selection.c	2008-01-04 23:11:04.000000000 +0100
+++ nedit_mod/source/selection.c	2008-01-19 00:43:29.000000000 +0100
@@ -320,7 +320,7 @@
        guranteed to be available, but in practice is there and does work. */
 #if defined(DONT_HAVE_GLOB) || defined(VMS)
     /* Open the file */
-    if (ParseFilename(nameText, filename, pathname) != 0) {
+    if (ParseFilename(nameText, filename, pathname, window->path) != 0) {
         XBell(TheDisplay, 0);
 	return;
     }	
@@ -330,14 +330,15 @@
     { char **nameList = NULL;
       int i, nFiles = 0, maxFiles = 30;
 
-      if (ParseFilename(nameText, filename, pathname) != 0) {
+      if (ParseFilename(nameText, filename, pathname, window->path) != 0) {
            XBell(TheDisplay, 0);
 	   return;
       }
       _XmOSGetDirEntries(pathname, filename, XmFILE_ANY_TYPE, False, True,
 	      &nameList, &nFiles, &maxFiles);
       for (i=0; i<nFiles; i++) {
-	  if (ParseFilename(nameList[i], filename, pathname) != 0) {
+          if (ParseFilename(nameList[i], filename, pathname,
+                window->path) != 0) {
 	      XBell(TheDisplay, 0);
 	  }
         else {
@@ -356,7 +357,8 @@
 
       glob(nameText, GLOB_NOCHECK, NULL, &globbuf);
       for (i=0; i<(int)globbuf.gl_pathc; i++) {
-	  if (ParseFilename(globbuf.gl_pathv[i], filename, pathname) != 0)
+          if (ParseFilename(globbuf.gl_pathv[i], filename, pathname,
+                window->path) != 0)
 	      XBell(TheDisplay, 0);
 	  else
     	      EditExistingFile(GetPrefOpenInTab()? window : NULL, 
diff -ur nedit_official/source/server.c nedit_mod/source/server.c
--- nedit_official/source/server.c	2007-12-31 12:12:43.000000000 +0100
+++ nedit_mod/source/server.c	2008-01-19 00:43:29.000000000 +0100
@@ -454,7 +454,7 @@
 	   existing window, or opening if they don't exist */
 	editFlags = (readFlag ? PREF_READ_ONLY : 0) | CREATE |
 		(createFlag ? SUPPRESS_CREATE_WARN : 0);
-	if (ParseFilename(fullname, filename, pathname) != 0) {
+        if (ParseFilename(fullname, filename, pathname, NULL) != 0) {
 	   fprintf(stderr, "NEdit: invalid file name\n");
            deleteFileClosedProperty2(filename, pathname);
 	   break;
diff -ur nedit_official/source/tags.c nedit_mod/source/tags.c
--- nedit_official/source/tags.c	2008-01-05 03:24:13.000000000 +0100
+++ nedit_mod/source/tags.c	2008-01-19 00:43:29.000000000 +0100
@@ -254,7 +254,7 @@
     else
         sprintf(newfile,"%s%s", path, file);
     
-    NormalizePathname(newfile);
+    NormalizePathname(newfile, NULL);
         
     for (t = table[addr]; t; t = t->next) {
         if (strcmp(name,t->name)) continue;
@@ -265,7 +265,7 @@
         if (*t->file != '/') {
             char tmpfile[MAXPATHLEN];
             sprintf(tmpfile, "%s%s", t->path, t->file);
-            NormalizePathname(tmpfile);
+            NormalizePathname(tmpfile, NULL);
             if (strcmp(newfile, tmpfile)) continue;
         }
         return 0;
@@ -365,7 +365,7 @@
          }   
         strcat(pathName, "/");
         strcat(pathName, filename);
-        NormalizePathname(pathName);
+        NormalizePathname(pathName, NULL);
 
         for (t = FileList; t && strcmp(t->filename, pathName); t = t->next);
         if (t) {
@@ -433,7 +433,7 @@
         } else {
             strcpy(pathName,filename);
         }
-        NormalizePathname(pathName);
+        NormalizePathname(pathName, NULL);
 
         for (t = FileList; t && strcmp(t->filename,pathName); t = t->next);
         if (t) {
@@ -504,7 +504,7 @@
         } else {
             strcpy(pathName,filename);
         }
-        NormalizePathname(pathName);
+        NormalizePathname(pathName, NULL);
 
         for (last=NULL,t = FileList; t; last = t,t = t->next) {
             if (strcmp(t->filename, pathName))
@@ -734,7 +734,7 @@
        return 0;
     }
 
-    ParseFilename(resolvedTagsFile, NULL, tagPath);
+    ParseFilename(resolvedTagsFile, NULL, tagPath, NULL);
 
     /* Read the file and store its contents */
     while (fgets(line, MAXLINE, fp)) {
@@ -1147,7 +1147,7 @@
             sprintf(tagFiles[nMatches],"%s%s",tagPath,fileToSearch);
         strcpy(tagSearch[nMatches],searchString);
         tagPosInf[nMatches]=startPos;
-        ParseFilename(tagFiles[nMatches], filename, pathname);
+        ParseFilename(tagFiles[nMatches], filename, pathname, NULL);
         /* Is this match in the current file?  If so, use it! */
         if (GetPrefSmartTags() && !strcmp(window->filename,filename)
                                && !strcmp(window->path,pathname)   ) {
@@ -1204,7 +1204,7 @@
         }
 
         for (i=0; i<nMatches; i++) {
-            ParseFilename(tagFiles[i], filename, pathname);
+            ParseFilename(tagFiles[i], filename, pathname, NULL);
             if ((i<nMatches-1 && !strcmp(tagFiles[i],tagFiles[i+1])) ||
                     (i>0 && !strcmp(tagFiles[i],tagFiles[i-1]))) {
                 if(*(tagSearch[i]) && (tagPosInf[i] != -1)) { /* etags */
@@ -1327,7 +1327,7 @@
     char *message;
     
     /* 1. Open the target file */
-    NormalizePathname(tagFiles[i]);
+    NormalizePathname(tagFiles[i], NULL);
     fp = fopen(tagFiles[i], "r");
     if (fp == NULL) {
         DialogF(DF_ERR, parent, 1, "Error opening File", "Error opening %s",
@@ -1446,7 +1446,7 @@
     WindowInfo *windowToSearch;
     WindowInfo *parentWindow = WidgetToWindow(parent);
     
-    ParseFilename(tagFiles[i],filename,pathname);
+    ParseFilename(tagFiles[i], filename, pathname, NULL);
     /* open the file containing the definition */
     EditExistingFile(parentWindow, filename, pathname, 0, NULL, False, 
     	    NULL, GetPrefOpenInTab(), False);
@@ -1993,7 +1993,7 @@
 #endif
 
     /* Get the path to the tips file */
-    ParseFilename(resolvedTipsFile, NULL, tipPath);
+    ParseFilename(resolvedTipsFile, NULL, tipPath, NULL);
 
     /* Open the file */
     if ((fp = fopen(resolvedTipsFile, "r")) == NULL)
diff -ur nedit_official/util/fileUtils.c nedit_mod/util/fileUtils.c
--- nedit_official/util/fileUtils.c	2007-12-31 12:12:44.000000000 +0100
+++ nedit_mod/util/fileUtils.c	2008-01-19 00:48:37.000000000 +0100
@@ -88,8 +88,8 @@
 ** least MAXPATHLEN chars long.
 ** To skip setting filename or pathname pass NULL for that argument.
 */
-int
-ParseFilename(const char *fullname, char *filename, char *pathname)
+int ParseFilename(const char *fullname, char *filename, char *pathname,
+        const char *relpath)
 {
     int fullLen = strlen(fullname);
     int i, pathLen, fileLen;
@@ -138,7 +138,7 @@
 
 #ifndef VMS /* UNIX specific... Modify at a later date for VMS */
     if(pathname) {
-	if (NormalizePathname(pathname)) {
+        if (NormalizePathname(pathname, relpath)) {
 	    return 1; /* pathname too long */
 	}
 	pathLen = strlen(pathname);
@@ -271,7 +271,7 @@
 	} else {
 	    strcpy(pathBuf, resolveBuf);
 	}
-	NormalizePathname(pathBuf);
+        NormalizePathname(pathBuf, NULL);
 	pathIn=pathBuf;
     }
 
@@ -287,9 +287,10 @@
 **  FIXME: Documentation
 **  FIXME: Change return value to False and True.
 */
-int NormalizePathname(char *pathname)
+int NormalizePathname(char *pathname, const char *relpath)
 {
-    /* if this is a relative pathname, prepend current directory */
+    /* if this is a relative pathname, prepend relpath */
+    /* if relpath is a relative path, prepend the current directory */
 #ifdef __EMX__
     /* OS/2, ...: welcome to the world of drive letters ... */
     if (!_fnisabs(pathname)) {
@@ -298,12 +299,13 @@
 #endif
         char *oldPathname;
         size_t len;
+        int useRelpath = (relpath && *relpath);
 
         /* make a copy of pathname to work from */
 	oldPathname=(char *)malloc(strlen(pathname)+1);
 	strcpy(oldPathname, pathname);
 	/* get the working directory and prepend to the path */
-	strcpy(pathname, GetCurrentDir());
+        strcpy(pathname, useRelpath ? relpath : GetCurrentDir());
 
 	/* check for trailing slash, or pathname being root dir "/":
 	   don't add a second '/' character as this may break things
@@ -320,6 +322,14 @@
         }
 	strcat(pathname, oldPathname);
 	free(oldPathname);
+        if (useRelpath)
+        {
+            /* make sure our relative path wasn't relative to start with */
+            if (CompressPathname(pathname) == 0)
+                return NormalizePathname(pathname, NULL);
+            else
+                return 1; /* some non-zero value */
+        }
     }
 
     /* compress out .. and . */
@@ -472,7 +482,7 @@
 /*
 ** Return 0 if everything's fine, 1 else.
 */
-int NormalizePathname(char *pathname)
+int NormalizePathname(char *pathname, const char *relpath)
 {
     return 0;
 }
diff -ur nedit_official/util/fileUtils.h nedit_mod/util/fileUtils.h
--- nedit_official/util/fileUtils.h	2004-11-09 22:58:45.000000000 +0100
+++ nedit_mod/util/fileUtils.h	2008-01-19 00:43:29.000000000 +0100
@@ -30,11 +30,12 @@
 
 enum fileFormats {UNIX_FILE_FORMAT, DOS_FILE_FORMAT, MAC_FILE_FORMAT};
 
-int ParseFilename(const char *fullname, char *filename, char *pathname);
+int ParseFilename(const char *fullname, char *filename, char *pathname,
+        const char *relpath);
 int ExpandTilde(char *pathname);
 const char* GetTrailingPathComponents(const char* path,
                                       int noOfComponents);
-int NormalizePathname(char *pathname);
+int NormalizePathname(char *pathname, const char *relpath);
 int CompressPathname(char *pathname);
 int ResolvePath(const char * pathIn, char * pathResolved); 
 
diff -ur nedit_official/util/getfiles.c nedit_mod/util/getfiles.c
--- nedit_official/util/getfiles.c	2006-08-22 15:23:02.000000000 +0200
+++ nedit_mod/util/getfiles.c	2008-01-19 00:43:29.000000000 +0100
@@ -1024,7 +1024,7 @@
     selectPos = 0;
     for (i=0; i<nItems; i++) {
     	XmStringGetLtoR(items[i], XmSTRING_DEFAULT_CHARSET, &itemString);
-    	if (ParseFilename(itemString, name, path) != 0) {
+        if (ParseFilename(itemString, name, path, NULL) != 0) {
 	   XtFree(itemString);
 	   return;
 	}
