/* $LynxId: LYEdit.c,v 1.43 2021/06/09 21:39:57 tom Exp $ */
#include <HTUtils.h>
#include <HTParse.h>
#include <HTAlert.h>
#include <LYCurses.h>
#include <LYUtils.h>
#include <LYGlobalDefs.h>
#include <LYStrings.h>
#include <LYEdit.h>
#ifdef VMS
#include <unixio.h>
#endif /* VMS */

#include <LYLeaks.h>
#include <www_wait.h>

#ifdef DOSPATH
/*
 *  The edit.com program of MS-DOS does not allow quoted arguments.
 *  For DJGPP check the DOS version and if greater than 6.22 like
 *  Win95 and greather then allow quoted arguments else not.
 */
# ifdef __DJGPP__
#  include <dos.h>
#  if defined (__GNUC__) && (__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 8))
#   define __gnuc_extension__  __extension__
#  else
#   define __gnuc_extension__
#  endif
#  define HTAddParamWithProperQuoting(COMMAND, FORMAT, PARAMETERS, FILENAME)  \
   (__gnuc_extension__                                                        \
     ({                                                                       \
        if (_get_dos_version(1) < 0x0617)  /*  MSDOS 6.22 or minor.  */       \
          HTAddToCmd((COMMAND), (FORMAT), (PARAMETERS), (FILENAME));          \
        else                                                                  \
          HTAddParam((COMMAND), (FORMAT), (PARAMETERS), (FILENAME));          \
     })                                                                       \
   )
# else   /*  !__DJGPP__  */
#  define HTAddParamWithProperQuoting  HTAddParam
# endif  /*  !__DJGPP__  */
#endif  /*  DOSPATH  */

BOOLEAN editor_can_position(void)
{
    char *value;
    HTList *p = positionable_editor;
    static const char *table[] =
    {
#ifdef VMS
	"sedt",
#else
	"emacs",		/* + xemacs */
	"jed",
	"jmacs",
	"joe",			/* + rjoe */
	"jove",
	"jstar",
	"nano",
	"pico",			/* + jpico */
	"vi"			/* + vim, xvi, vile, elvis, view... + likely false matches */
#endif
    };
    unsigned n;

    for (n = 0; n < TABLESIZE(table); n++) {
	if (LYstrstr(editor, table[n]) != 0) {
	    return TRUE;
	}
    }
    /*
     * This really isn't right.  LYstrstr() might be too lax,
     * but this should at least match basename to basename...
     */
    if (positionable_editor != NULL) {
	while ((value = (char *) HTList_nextObject(p)) != NULL) {
	    if (strcmp(editor, value) == 0) {
		return TRUE;
	    }
	}
    }
    return FALSE;
}

/*
 * In edit mode invoke the given (or default) editor to display and edit the
 * current file.  For editors listed in 'editor_can_position()', Lynx will open
 * the file to the same line that the screen cursor is on (or close...) when
 * editing is invoked.
 *
 * Returns FALSE if file is uneditable.
 */
int edit_current_file(char *newfile,
		      int cur,
		      int lineno)
{
    int result = FALSE;
    char *filename = NULL;

#if !(defined(VMS) || defined(USE_DOS_DRIVES))
    char *colon;
#endif
    char *number_sign;
    char position[80];

#if defined(VMS) || defined(CANT_EDIT_UNWRITABLE_FILES)
    FILE *fp;
#endif

    CTRACE((tfp, "edit_current_file(newfile=%s, cur=%d, lineno=%d)\n",
	    newfile, cur, lineno));

    /*
     * If it's a remote file then we can't edit it.
     */
    if (!LYisLocalFile(newfile)) {
	HTUserMsg(CANNOT_EDIT_REMOTE_FILES);
	return FALSE;
    }

    /*
     * If there's a fragment, trim it.  - FM
     */
    number_sign = trimPoundSelector(newfile);

    /*
     * On Unix, first try to open it as a completely referenced file, then via
     * the path alone.
     *
     * On VMS, only try the path.
     */
#if defined (VMS) || defined (USE_DOS_DRIVES)
    filename = HTParse(newfile, "", PARSE_PATH + PARSE_PUNCTUATION);
    HTUnEscape(filename);
    StrAllocCopy(filename, HTSYS_name(filename));
    if (!LYCanReadFile(filename)) {
#ifdef SH_EX
	HTUserMsg2(COULD_NOT_EDIT_FILE, filename);
#else
	HTAlert(COULD_NOT_ACCESS_FILE);
#endif
	CTRACE((tfp, "filename: '%s'\n", filename));
	goto done;
    }
#else /* something like UNIX */
    if (StrNCmp(newfile, "file://localhost/", 16) == 0)
	colon = newfile + 16;
    else
	colon = StrChr(newfile, ':');
    StrAllocCopy(filename, (colon + 1));
    HTUnEscape(filename);
    if (!LYCanReadFile(filename)) {
	FREE(filename);
	filename = HTParse(newfile, "", PARSE_PATH + PARSE_PUNCTUATION);
	HTUnEscape(filename);
	if (!LYCanReadFile(HTSYS_name(filename))) {
	    HTAlert(COULD_NOT_ACCESS_FILE);
	    goto done;
	}
    }
#endif

#if defined(VMS) || defined(CANT_EDIT_UNWRITABLE_FILES)
    /*
     * Don't allow editing if user lacks append access.
     */
    if ((fp = fopen(filename, TXT_A)) == NULL) {
	HTUserMsg(NOAUTH_TO_EDIT_FILE);
	goto done;
    }
    fclose(fp);
#endif /* VMS || CANT_EDIT_UNWRITABLE_FILES */

    /*
     * Make sure cur is at least zero.  - FM
     */
    if (cur < 0) {
	cur = 0;
    }

    /*
     * Set up the command for the editor.  - FM
     */
    if (lineno >= 0) {
	*position = 0;
#ifdef VMS
	lineno--;
#endif
	lineno += (nlinks ? links[cur].ly : 0);
	if (lineno > 0)
	    sprintf(position, "%d", lineno);
    } else {
	*position = '\0';
    }

    edit_temporary_file(filename, position, NULL);
    result = TRUE;

  done:
    /*
     * Restore the fragment if there was one.  - FM
     */
    restorePoundSelector(number_sign);

    FREE(filename);
    CTRACE((tfp, "edit_current_file returns %d\n", result));
    return (result);
}

void edit_temporary_file(char *filename,
			 const char *position,
			 const char *message)
{
#ifdef UNIX
    struct stat stat_info;
#endif
    const char *format = "%s %s";
    char *command = NULL;
    const char *editor_arg = "";
    int params = 1;
    int rv;

    if (LYstrstr(editor, "pico")) {
	editor_arg = " -t";	/* No prompt for filename to use */
    }
    if (editor_can_position() && *position) {
#ifdef VMS
	format = "%s %s -%s%s";
	HTAddXpand(&command, format, params++, editor);
	HTAddParam(&command, format, params++, filename);
	HTAddParam(&command, format, params++, position);
	HTAddParam(&command, format, params++, editor_arg);
	HTEndParam(&command, format, params);
#else
	format = "%s +%s%s %s";
	HTAddXpand(&command, format, params++, editor);
	HTAddParam(&command, format, params++, position);
	HTAddParam(&command, format, params++, editor_arg);
	HTAddParam(&command, format, params++, filename);
	HTEndParam(&command, format, params);
#endif
    }
#ifdef DOSPATH
    else if (StrNCmp(editor, "VZ", 2) == 0) {
	/* for Vz editor */
	format = "%s %s -%s";
	HTAddXpand(&command, format, params++, editor);
	HTAddParam(&command, format, params++, HTDOS_short_name(filename));
	HTAddParam(&command, format, params++, position);
	HTEndParam(&command, format, params);
    } else if (StrNCmp(editor, "edit", 4) == 0) {
	/* for standard editor */
	HTAddXpand(&command, format, params++, editor);
	HTAddParamWithProperQuoting(&command, format, params++, HTDOS_short_name(filename));
	HTEndParam(&command, format, params);
    }
#endif
    else {
#ifdef _WINDOWS
	if (StrChr(editor, ' '))
	    HTAddXpand(&command, format, params++, HTDOS_short_name(editor));
	else
	    HTAddXpand(&command, format, params++, editor);
#else
	HTAddXpand(&command, format, params++, editor);
#endif
	HTAddParam(&command, format, params++, filename);
	HTEndParam(&command, format, params);
    }
    if (message != NULL) {
	_statusline(message);
    }

    CTRACE((tfp, "LYEdit: %s\n", command));
    CTRACE_SLEEP(MessageSecs);

    stop_curses();

#ifdef UNIX
    set_errno(0);
#endif
    if ((rv = LYSystem(command)) != 0) {	/* Spawn Editor */
	start_curses();
	/*
	 * If something went wrong, we should probably return soon; currently
	 * we don't, but at least put out a message.  - kw
	 */
	{
#if defined(UNIX) && defined(WIFEXITED)
	    int save_err = errno;

	    CTRACE((tfp, "ExtEditForm: system() returned %d (0x%x), %s\n",
		    rv, (unsigned) rv,
		    (save_err
		     ? LYStrerror(save_err)
		     : "reason unknown")));
	    LYFixCursesOn("show error warning:");
	    if (rv == -1) {
		HTUserMsg2(gettext("Error starting editor, %s"),
			   LYStrerror(save_err));
	    } else if (WIFSIGNALED(rv)) {
		HTAlwaysAlert(NULL, gettext("Editor killed by signal"));
	    } else if (WIFEXITED(rv) && WEXITSTATUS(rv) != 127) {
		char exitcode[80];

		sprintf(exitcode, "%d", WEXITSTATUS(rv));
		HTUserMsg2(gettext("Editor returned with error status %s"),
			   exitcode);
	    } else
#endif
		HTAlwaysAlert(NULL, ERROR_SPAWNING_EDITOR);
	}
    } else {
	start_curses();
    }
#ifdef UNIX
    /*
     * Delete backup file, if that's your style.
     */
    HTSprintf0(&command, "%s~", filename);
    if (stat(command, &stat_info) == 0)
	remove(command);
#endif
    FREE(command);
}
