Blame src/complete.cc

Packit 8f70b4
/*
Packit 8f70b4
 * lftp - file transfer program
Packit 8f70b4
 *
Packit 8f70b4
 * Copyright (c) 1996-2017 by Alexander V. Lukyanov (lav@yars.free.net)
Packit 8f70b4
 *
Packit 8f70b4
 * This program is free software; you can redistribute it and/or modify
Packit 8f70b4
 * it under the terms of the GNU General Public License as published by
Packit 8f70b4
 * the Free Software Foundation; either version 3 of the License, or
Packit 8f70b4
 * (at your option) any later version.
Packit 8f70b4
 *
Packit 8f70b4
 * This program is distributed in the hope that it will be useful,
Packit 8f70b4
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 8f70b4
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit 8f70b4
 * GNU General Public License for more details.
Packit 8f70b4
 *
Packit 8f70b4
 * You should have received a copy of the GNU General Public License
Packit 8f70b4
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
Packit 8f70b4
 */
Packit 8f70b4
Packit 8f70b4
#include <config.h>
Packit 8f70b4
Packit 8f70b4
#include "trio.h"
Packit 8f70b4
#include <sys/types.h>
Packit 8f70b4
#include <sys/stat.h>
Packit 8f70b4
#include <time.h>
Packit 8f70b4
#include <unistd.h>
Packit 8f70b4
#include <sys/time.h>
Packit 8f70b4
#include <assert.h>
Packit 8f70b4
#include "xmalloc.h"
Packit 8f70b4
#include "FileAccess.h"
Packit 8f70b4
#include "CmdExec.h"
Packit 8f70b4
#include "alias.h"
Packit 8f70b4
#include "SignalHook.h"
Packit 8f70b4
#include "CharReader.h"
Packit 8f70b4
#include "LsCache.h"
Packit 8f70b4
#include "complete.h"
Packit 8f70b4
#include "lftp_rl.h"
Packit 8f70b4
#include "url.h"
Packit 8f70b4
#include "ResMgr.h"
Packit 8f70b4
#include "ColumnOutput.h"
Packit 8f70b4
#include "FileSetOutput.h"
Packit 8f70b4
#include "OutputJob.h"
Packit 8f70b4
#include "misc.h"
Packit 8f70b4
Packit 8f70b4
CDECL_BEGIN
Packit 8f70b4
#include <glob.h>
Packit 8f70b4
Packit 8f70b4
#define USE_VARARGS 1
Packit 8f70b4
#define PREFER_STDARG 1
Packit 8f70b4
#include <readline/readline.h>
Packit 8f70b4
CDECL_END
Packit 8f70b4
Packit 8f70b4
#ifndef GLOB_PERIOD
Packit 8f70b4
# define GLOB_PERIOD 0
Packit 8f70b4
#endif
Packit 8f70b4
Packit 8f70b4
static char *bash_dequote_filename (const char *text, int quote_char);
Packit 8f70b4
static int lftp_char_is_quoted(const char *string,int eindex);
Packit 8f70b4
Packit 8f70b4
static int len;    // lenght of the word to complete
Packit 8f70b4
static int cindex; // index in completion array
Packit 8f70b4
static const char *const *array;
Packit 8f70b4
static char **vars=NULL;
Packit 8f70b4
static FileSet *glob_res=NULL;
Packit 8f70b4
static bool inhibit_tilde;
Packit 8f70b4
Packit 8f70b4
static bool shell_cmd;
Packit 8f70b4
static bool quote_glob;
Packit 8f70b4
static bool quote_glob_basename;
Packit 8f70b4
Packit 8f70b4
char *command_generator(const char *text,int state)
Packit 8f70b4
{
Packit 8f70b4
   const char *name;
Packit 8f70b4
   static const Alias *alias;
Packit 8f70b4
Packit 8f70b4
   /* If this is a new word to complete, initialize now. */
Packit 8f70b4
   if(!state)
Packit 8f70b4
   {
Packit 8f70b4
      cindex=0;
Packit 8f70b4
      alias=Alias::base;
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
   /* Return the next name which partially matches from the command list. */
Packit 8f70b4
   while ((name=CmdExec::CmdByIndex(cindex))!=0)
Packit 8f70b4
   {
Packit 8f70b4
      cindex++;
Packit 8f70b4
      if(name[0]=='.' && len==0)
Packit 8f70b4
	 continue;   // skip hidden commands
Packit 8f70b4
      if(strncasecmp(name,text,len)==0)
Packit 8f70b4
	 return(xstrdup(name));
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
   while(alias)
Packit 8f70b4
   {
Packit 8f70b4
      const Alias *tmp=alias;
Packit 8f70b4
      alias=alias->next;
Packit 8f70b4
      if(strncasecmp(tmp->alias,text,len)==0)
Packit 8f70b4
         return(xstrdup(tmp->alias));
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
   /* If no names matched, then return NULL. */
Packit 8f70b4
   return(NULL);
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
static char *file_generator(const char *text,int state)
Packit 8f70b4
{
Packit 8f70b4
   /* If this is a new word to complete, initialize now. */
Packit 8f70b4
   if(!state)
Packit 8f70b4
      cindex=0;
Packit 8f70b4
Packit 8f70b4
   if(glob_res==NULL)
Packit 8f70b4
      return NULL;
Packit 8f70b4
Packit 8f70b4
   while((*glob_res)[cindex])
Packit 8f70b4
   {
Packit 8f70b4
      const char *name=(*glob_res)[cindex++]->name;
Packit 8f70b4
Packit 8f70b4
      if(!name[0])
Packit 8f70b4
	 continue;
Packit 8f70b4
      return(xstrdup(name));
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
   return NULL;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
static bool bookmark_prepend_bm;
Packit 8f70b4
static char *bookmark_generator(const char *text,int s)
Packit 8f70b4
{
Packit 8f70b4
   static int state;
Packit 8f70b4
   const char *t;
Packit 8f70b4
   if(!s)
Packit 8f70b4
   {
Packit 8f70b4
      state=0;
Packit 8f70b4
      lftp_bookmarks.Rewind();
Packit 8f70b4
   }
Packit 8f70b4
   for(;;)
Packit 8f70b4
   {
Packit 8f70b4
      switch(state)
Packit 8f70b4
      {
Packit 8f70b4
      case 0:
Packit 8f70b4
	 t=lftp_bookmarks.CurrentKey();
Packit 8f70b4
	 if(!t)
Packit 8f70b4
	 {
Packit 8f70b4
	    state=1;
Packit 8f70b4
	    break;
Packit 8f70b4
	 }
Packit 8f70b4
	 if(!lftp_bookmarks.Next())
Packit 8f70b4
	    state=1;
Packit 8f70b4
	 if(bookmark_prepend_bm)
Packit 8f70b4
	 {
Packit 8f70b4
	    xstring& e=xstring::get_tmp("bm:");
Packit 8f70b4
	    t=e.append_url_encoded(t,URL_HOST_UNSAFE);
Packit 8f70b4
	 }
Packit 8f70b4
	 if(strncmp(t,text,len)==0)
Packit 8f70b4
	    return xstrdup(t);
Packit 8f70b4
	 break;
Packit 8f70b4
      case 1:
Packit 8f70b4
	 return 0;
Packit 8f70b4
      }
Packit 8f70b4
   }
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
static char *array_generator(const char *text,int state)
Packit 8f70b4
{
Packit 8f70b4
   const char *name;
Packit 8f70b4
Packit 8f70b4
   /* If this is a new word to complete, initialize now. */
Packit 8f70b4
   if(!state)
Packit 8f70b4
      cindex=0;
Packit 8f70b4
Packit 8f70b4
   if(array==NULL)
Packit 8f70b4
      return NULL;
Packit 8f70b4
Packit 8f70b4
   while((name=array[cindex++])!=NULL)
Packit 8f70b4
   {
Packit 8f70b4
      if(!name[0])
Packit 8f70b4
	 continue;
Packit 8f70b4
      if(strncmp(name,text,len)==0)
Packit 8f70b4
	 return(xstrdup(name));
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
   array=NULL;
Packit 8f70b4
   return NULL;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
static char *vars_generator(const char *text,int state)
Packit 8f70b4
{
Packit 8f70b4
   const char *name;
Packit 8f70b4
Packit 8f70b4
   /* If this is a new word to complete, initialize now. */
Packit 8f70b4
   if(!state)
Packit 8f70b4
      cindex=0;
Packit 8f70b4
Packit 8f70b4
   if(vars==NULL)
Packit 8f70b4
      return NULL;
Packit 8f70b4
Packit 8f70b4
   while((name=vars[cindex++])!=NULL)
Packit 8f70b4
   {
Packit 8f70b4
      if(!name[0])
Packit 8f70b4
	 continue;
Packit 8f70b4
      char *text0=string_alloca(len+2);
Packit 8f70b4
      strncpy(text0,text,len);
Packit 8f70b4
      text0[len]=0;
Packit 8f70b4
      if(ResMgr::VarNameCmp(name,text0)!=ResMgr::DIFFERENT)
Packit 8f70b4
	 return(xstrdup(name));
Packit 8f70b4
      if(strchr(text0,':')==0)
Packit 8f70b4
      {
Packit 8f70b4
	 strcat(text0,":");
Packit 8f70b4
	 if(ResMgr::VarNameCmp(name,text0)!=ResMgr::DIFFERENT)
Packit 8f70b4
	    return(xstrdup(name));
Packit 8f70b4
      }
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
   return NULL;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
static bool not_dir(char *f)
Packit 8f70b4
{
Packit 8f70b4
   struct stat st;
Packit 8f70b4
   f=tilde_expand(f);
Packit 8f70b4
   bool res=(stat(f,&st)==-1 || !S_ISDIR(st.st_mode));
Packit 8f70b4
   free(f);
Packit 8f70b4
   return res;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
int ignore_non_dirs(char **matches)
Packit 8f70b4
{
Packit 8f70b4
   // filter out non-dirs.
Packit 8f70b4
   int out=1;
Packit 8f70b4
   for(int i=1; matches[i]; i++)
Packit 8f70b4
   {
Packit 8f70b4
      if(!not_dir(matches[i]))
Packit 8f70b4
	 matches[out++]=matches[i];
Packit 8f70b4
      else
Packit 8f70b4
	 free(matches[i]);
Packit 8f70b4
   }
Packit 8f70b4
   matches[out]=0;
Packit 8f70b4
   if(out==1)
Packit 8f70b4
   {
Packit 8f70b4
      // we have only the LCD prefix. Handle it carefully.
Packit 8f70b4
      char *f=matches[0];
Packit 8f70b4
      int len=strlen(f);
Packit 8f70b4
      if((len>2 && f[len-1]=='/') // all files, no dirs.
Packit 8f70b4
      || not_dir(f))		 // or single non dir.
Packit 8f70b4
      {
Packit 8f70b4
	 // all files, no dirs.
Packit 8f70b4
	 free(f);
Packit 8f70b4
	 matches[0]=0;
Packit 8f70b4
      }
Packit 8f70b4
   }
Packit 8f70b4
   return 0;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
static const char *find_word(const char *p)
Packit 8f70b4
{
Packit 8f70b4
   while(CmdExec::is_space(*p))
Packit 8f70b4
      p++;
Packit 8f70b4
   return p;
Packit 8f70b4
}
Packit 8f70b4
static bool copy_word_skip(const char **p_in,char *buf,int n)
Packit 8f70b4
{
Packit 8f70b4
   const char *&p=*p_in;
Packit 8f70b4
   char in_quotes=0;
Packit 8f70b4
   for(;;)
Packit 8f70b4
   {
Packit 8f70b4
      if(!*p)
Packit 8f70b4
	 break;
Packit 8f70b4
      if(in_quotes)
Packit 8f70b4
      {
Packit 8f70b4
	 if(*p==in_quotes)
Packit 8f70b4
	 {
Packit 8f70b4
	    in_quotes=0,p++;
Packit 8f70b4
	    continue;
Packit 8f70b4
	 }
Packit 8f70b4
	 else if(*p=='\\' && CmdExec::quotable(p[1],in_quotes))
Packit 8f70b4
	    p++;
Packit 8f70b4
	 if(buf && n>0)
Packit 8f70b4
	    *buf++=*p,n--;
Packit 8f70b4
	 p++;
Packit 8f70b4
	 continue;
Packit 8f70b4
      }
Packit 8f70b4
      if(CmdExec::is_space(*p))
Packit 8f70b4
	 break;
Packit 8f70b4
      if(*p=='\\' && CmdExec::quotable(p[1],in_quotes))
Packit 8f70b4
	 p++;
Packit 8f70b4
      else if(CmdExec::is_quote(*p))
Packit 8f70b4
      {
Packit 8f70b4
	 in_quotes=*p++;
Packit 8f70b4
	 continue;
Packit 8f70b4
      }
Packit 8f70b4
      if(buf && n>0)
Packit 8f70b4
	 *buf++=*p,n--;
Packit 8f70b4
      p++;
Packit 8f70b4
   }
Packit 8f70b4
   if(n>0 && buf)
Packit 8f70b4
      *buf=0;
Packit 8f70b4
   return n>0;
Packit 8f70b4
}
Packit 8f70b4
// returns false when buffer overflows
Packit 8f70b4
static bool copy_word(char *buf,const char *p,int n)
Packit 8f70b4
{
Packit 8f70b4
   return copy_word_skip(&p,buf,n);
Packit 8f70b4
}
Packit 8f70b4
static const char *skip_word(const char *p)
Packit 8f70b4
{
Packit 8f70b4
   copy_word_skip(&p,0,0);
Packit 8f70b4
   return p;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
enum completion_type
Packit 8f70b4
{
Packit 8f70b4
   LOCAL, LOCAL_DIR, REMOTE_FILE, REMOTE_DIR, BOOKMARK, COMMAND,
Packit 8f70b4
   STRING_ARRAY, VARIABLE, NO_COMPLETION
Packit 8f70b4
};
Packit 8f70b4
Packit 8f70b4
// cmd: ptr to command line being completed
Packit 8f70b4
// start: location of the word being completed
Packit 8f70b4
static completion_type cmd_completion_type(const char *cmd,int start)
Packit 8f70b4
{
Packit 8f70b4
   const char *w=0;
Packit 8f70b4
   char buf[20];  // no commands longer
Packit 8f70b4
   TouchedAlias *used_aliases=0;
Packit 8f70b4
Packit 8f70b4
   // try to guess whether the completion word is remote
Packit 8f70b4
   for(;;)
Packit 8f70b4
   {
Packit 8f70b4
      w=find_word(cmd);
Packit 8f70b4
      if(w-cmd == start) // first word is command
Packit 8f70b4
	 return COMMAND;
Packit 8f70b4
      if(w[0]=='!')
Packit 8f70b4
	 shell_cmd=true;
Packit 8f70b4
      if(w[0]=='#')
Packit 8f70b4
	 return NO_COMPLETION;
Packit 8f70b4
      if(w[0]=='!')
Packit 8f70b4
      {
Packit 8f70b4
	 shell_cmd=quote_glob=true;
Packit 8f70b4
	 return LOCAL;
Packit 8f70b4
      }
Packit 8f70b4
      if(w[0]=='?')  // help
Packit 8f70b4
	 return COMMAND;
Packit 8f70b4
      if(w[0]=='(')
Packit 8f70b4
      {
Packit 8f70b4
	 start-=(w+1-cmd);
Packit 8f70b4
	 cmd=w+1;
Packit 8f70b4
	 continue;
Packit 8f70b4
      }
Packit 8f70b4
      if(!copy_word(buf,w,sizeof(buf))
Packit 8f70b4
      || buf[0]==0)
Packit 8f70b4
      {
Packit 8f70b4
	 TouchedAlias::FreeChain(used_aliases);
Packit 8f70b4
	 return LOCAL;
Packit 8f70b4
      }
Packit 8f70b4
      const char *alias=Alias::Find(buf);
Packit 8f70b4
      if(alias && !TouchedAlias::IsTouched(alias,used_aliases))
Packit 8f70b4
      {
Packit 8f70b4
	 used_aliases=new TouchedAlias(alias,used_aliases);
Packit 8f70b4
	 int buf_len=strlen(buf);
Packit 8f70b4
	 const char *cmd1=xstring::cat(alias,w+buf_len,NULL);
Packit 8f70b4
	 cmd=alloca_strdup(cmd1);
Packit 8f70b4
	 start=start-buf_len+strlen(alias);
Packit 8f70b4
	 continue;
Packit 8f70b4
      }
Packit 8f70b4
      const char *full=CmdExec::GetFullCommandName(buf);
Packit 8f70b4
      if(full!=buf)
Packit 8f70b4
	 strcpy(buf,full);
Packit 8f70b4
      TouchedAlias::FreeChain(used_aliases);
Packit 8f70b4
      break;
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
   for(const char *p=cmd+start; p>cmd; )
Packit 8f70b4
   {
Packit 8f70b4
      p--;
Packit 8f70b4
      if((*p=='>' || *p=='|')
Packit 8f70b4
      && !lftp_char_is_quoted(cmd,p-cmd))
Packit 8f70b4
	 return LOCAL;
Packit 8f70b4
      if(!CmdExec::is_space(*p))
Packit 8f70b4
	 break;
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
   if(!strcmp(buf,"shell"))
Packit 8f70b4
      shell_cmd=quote_glob=true;
Packit 8f70b4
   if(!strcmp(buf,"glob")
Packit 8f70b4
   || !strcmp(buf,"mget")
Packit 8f70b4
   || !strcmp(buf,"mput")
Packit 8f70b4
   || !strcmp(buf,"mrm"))
Packit 8f70b4
      quote_glob=true;
Packit 8f70b4
Packit 8f70b4
   if(!strcmp(buf,"cls"))
Packit 8f70b4
      quote_glob_basename=true;
Packit 8f70b4
Packit 8f70b4
   if(!strcmp(buf,"cd")
Packit 8f70b4
   || !strcmp(buf,"mkdir"))
Packit 8f70b4
      return REMOTE_DIR; /* append slash automatically */
Packit 8f70b4
Packit 8f70b4
   if(!strcmp(buf,"cat")
Packit 8f70b4
   || !strcmp(buf,"ls")
Packit 8f70b4
   || !strcmp(buf,"cls")
Packit 8f70b4
   || !strcmp(buf,"du")
Packit 8f70b4
   || !strcmp(buf,"edit")
Packit 8f70b4
   || !strcmp(buf,"find")
Packit 8f70b4
   || !strcmp(buf,"recls")
Packit 8f70b4
   || !strcmp(buf,"rels")
Packit 8f70b4
   || !strcmp(buf,"more")
Packit 8f70b4
   || !strcmp(buf,"mrm")
Packit 8f70b4
   || !strcmp(buf,"mv")
Packit 8f70b4
   || !strcmp(buf,"mmv")
Packit 8f70b4
   || !strcmp(buf,"nlist")
Packit 8f70b4
   || !strcmp(buf,"rm")
Packit 8f70b4
   || !strcmp(buf,"rmdir")
Packit 8f70b4
   || !strcmp(buf,"bzcat")
Packit 8f70b4
   || !strcmp(buf,"bzmore")
Packit 8f70b4
   || !strcmp(buf,"zcat")
Packit 8f70b4
   || !strcmp(buf,"zmore"))
Packit 8f70b4
      return REMOTE_FILE;
Packit 8f70b4
Packit 8f70b4
   if(!strcmp(buf,"open")
Packit 8f70b4
   || !strcmp(buf,"lftp"))
Packit 8f70b4
      return BOOKMARK;
Packit 8f70b4
Packit 8f70b4
   if(!strcmp(buf,"help"))
Packit 8f70b4
      return COMMAND;
Packit 8f70b4
Packit 8f70b4
   bool was_o=false;
Packit 8f70b4
   bool was_N=false;
Packit 8f70b4
   bool was_O=false;
Packit 8f70b4
   bool have_R=false;
Packit 8f70b4
   bool have_N=false;
Packit 8f70b4
   bool second=false;
Packit 8f70b4
   int second_start=-1;
Packit 8f70b4
   for(int i=start; i>4; i--)
Packit 8f70b4
   {
Packit 8f70b4
      if(!CmdExec::is_space(rl_line_buffer[i-1]))
Packit 8f70b4
	 break;
Packit 8f70b4
      if(!strncmp(rl_line_buffer+i-3,"-o",2) && CmdExec::is_space(rl_line_buffer[i-4]))
Packit 8f70b4
      {
Packit 8f70b4
	 was_o=true;
Packit 8f70b4
	 break;
Packit 8f70b4
      }
Packit 8f70b4
      if(!strncmp(rl_line_buffer+i-3,"-N",2) && CmdExec::is_space(rl_line_buffer[i-4]))
Packit 8f70b4
      {
Packit 8f70b4
	 was_N=true;
Packit 8f70b4
	 break;
Packit 8f70b4
      }
Packit 8f70b4
      if(i-14 >= 0 && !strncmp(rl_line_buffer+i-13, "--newer-than",12) && CmdExec::is_space(rl_line_buffer[i-14]))
Packit 8f70b4
      {
Packit 8f70b4
	 was_N=true;
Packit 8f70b4
	 break;
Packit 8f70b4
      }
Packit 8f70b4
      if(!strncmp(rl_line_buffer+i-3,"-O",2) && CmdExec::is_space(rl_line_buffer[i-4]))
Packit 8f70b4
      {
Packit 8f70b4
	 was_O=true;
Packit 8f70b4
	 break;
Packit 8f70b4
      }
Packit 8f70b4
   }
Packit 8f70b4
   w=skip_word(find_word(cmd));
Packit 8f70b4
   if(*w)
Packit 8f70b4
   {
Packit 8f70b4
      w=find_word(w);
Packit 8f70b4
      second_start=w-cmd;
Packit 8f70b4
      if(w-cmd==start)	// we complete second word
Packit 8f70b4
	 second=true;
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
   if(re_match(cmd," -[a-zA-Z]*R[a-zA-Z]* ",0))
Packit 8f70b4
      have_R=true;
Packit 8f70b4
Packit 8f70b4
   int arg=0;
Packit 8f70b4
   bool opt_stop=false;
Packit 8f70b4
   for(w=cmd; *w; )
Packit 8f70b4
   {
Packit 8f70b4
      w=find_word(w);
Packit 8f70b4
      if(w-cmd==start || w-cmd+CmdExec::is_quote(*w)==start)
Packit 8f70b4
	 break;
Packit 8f70b4
      if(w[0]!='-' || opt_stop)
Packit 8f70b4
	 arg++;
Packit 8f70b4
      else if(re_match(w,"^-[a-zA-Z]*N ",0))
Packit 8f70b4
	 have_N=true;
Packit 8f70b4
      if(!strncmp(w,"-- ",3))
Packit 8f70b4
	 opt_stop=true;
Packit 8f70b4
      w=skip_word(w);
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
   if(!strcmp(buf,"get")
Packit 8f70b4
   || !strcmp(buf,"pget")
Packit 8f70b4
   || !strcmp(buf,"get1"))
Packit 8f70b4
   {
Packit 8f70b4
      if(was_O)
Packit 8f70b4
	 return LOCAL_DIR;
Packit 8f70b4
      if(!was_o)
Packit 8f70b4
	 return REMOTE_FILE;
Packit 8f70b4
   }
Packit 8f70b4
   if(!strcmp(buf,"mget"))
Packit 8f70b4
      if(!was_O)
Packit 8f70b4
	 return REMOTE_FILE;
Packit 8f70b4
   if(!strcmp(buf,"put"))
Packit 8f70b4
      if(was_o)
Packit 8f70b4
	 return REMOTE_FILE;
Packit 8f70b4
   if(!strcmp(buf,"put")
Packit 8f70b4
   || !strcmp(buf,"mput"))
Packit 8f70b4
      if(was_O)
Packit 8f70b4
	 return REMOTE_DIR;
Packit 8f70b4
   if(!strcmp(buf,"mirror"))
Packit 8f70b4
   {
Packit 8f70b4
      if(was_N)
Packit 8f70b4
	 return LOCAL;
Packit 8f70b4
      if(have_N)
Packit 8f70b4
	 arg--;
Packit 8f70b4
      if(have_R ^ (arg!=1))
Packit 8f70b4
	 return LOCAL_DIR;
Packit 8f70b4
      if(arg>2)
Packit 8f70b4
	 return NO_COMPLETION;
Packit 8f70b4
      return REMOTE_DIR;
Packit 8f70b4
   }
Packit 8f70b4
   if(!strcmp(buf,"bookmark"))
Packit 8f70b4
   {
Packit 8f70b4
      if(second)
Packit 8f70b4
      {
Packit 8f70b4
	 array=bookmark_subcmd;
Packit 8f70b4
	 return STRING_ARRAY;
Packit 8f70b4
      }
Packit 8f70b4
      else
Packit 8f70b4
	 return BOOKMARK;
Packit 8f70b4
   }
Packit 8f70b4
   if(!strcmp(buf,"chmod"))
Packit 8f70b4
   {
Packit 8f70b4
      if(second)
Packit 8f70b4
	 return NO_COMPLETION;
Packit 8f70b4
      else
Packit 8f70b4
	 return REMOTE_FILE;
Packit 8f70b4
   }
Packit 8f70b4
   if(!strcmp(buf,"glob")
Packit 8f70b4
   || !strcmp(buf,"command")
Packit 8f70b4
   || !strcmp(buf,"queue"))
Packit 8f70b4
   {
Packit 8f70b4
      if(second)
Packit 8f70b4
	 return COMMAND;
Packit 8f70b4
      else
Packit 8f70b4
      {
Packit 8f70b4
	 // FIXME: infinite alias expansion is possible.
Packit 8f70b4
	 if(second_start>0 && start>second_start && (int)strlen(cmd)>second_start)
Packit 8f70b4
	    return cmd_completion_type(cmd+second_start,start-second_start);
Packit 8f70b4
	 return REMOTE_FILE;
Packit 8f70b4
      }
Packit 8f70b4
   }
Packit 8f70b4
   if(!strcmp(buf,"cache"))
Packit 8f70b4
   {
Packit 8f70b4
      if(second)
Packit 8f70b4
      {
Packit 8f70b4
	 array=cache_subcmd;
Packit 8f70b4
	 return STRING_ARRAY;
Packit 8f70b4
      }
Packit 8f70b4
      else
Packit 8f70b4
	 return NO_COMPLETION;
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
   if(!strcmp(buf,"set"))
Packit 8f70b4
   {
Packit 8f70b4
      if(second)
Packit 8f70b4
      {
Packit 8f70b4
         if(!vars)
Packit 8f70b4
            vars=ResMgr::Generator();
Packit 8f70b4
         return VARIABLE;
Packit 8f70b4
      }
Packit 8f70b4
      else
Packit 8f70b4
         return NO_COMPLETION;
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
   if(!strcmp(buf,"lcd"))
Packit 8f70b4
      return LOCAL_DIR;
Packit 8f70b4
Packit 8f70b4
   return LOCAL;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
static void glob_quote(char *out,const char *in,int len)
Packit 8f70b4
{
Packit 8f70b4
   while(len>0)
Packit 8f70b4
   {
Packit 8f70b4
      switch(*in)
Packit 8f70b4
      {
Packit 8f70b4
      case '*': case '?': case '[': case ']':
Packit 8f70b4
	 if(!quote_glob)
Packit 8f70b4
	    *out++='\\';
Packit 8f70b4
	 break;
Packit 8f70b4
      case '\\':
Packit 8f70b4
	 switch(in[1])
Packit 8f70b4
	 {
Packit 8f70b4
	 case '*': case '?': case '[': case ']': case '\\':
Packit 8f70b4
	    *out++=*in++;  // copy the backslash.
Packit 8f70b4
	    break;
Packit 8f70b4
	 default:
Packit 8f70b4
	    in++; // skip it.
Packit 8f70b4
	    break;
Packit 8f70b4
	 }
Packit 8f70b4
	 break;
Packit 8f70b4
      }
Packit 8f70b4
      *out++=*in;
Packit 8f70b4
      in++;
Packit 8f70b4
      len--;
Packit 8f70b4
   }
Packit 8f70b4
   *out=0;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
CmdExec *completion_shell;
Packit 8f70b4
int remote_completion=0;
Packit 8f70b4
Packit 8f70b4
static bool force_remote=false;
Packit 8f70b4
Packit 8f70b4
/* Attempt to complete on the contents of TEXT.  START and END show the
Packit 8f70b4
   region of TEXT that contains the word to complete.  We can use the
Packit 8f70b4
   entire line in case we want to do some simple parsing.  Return the
Packit 8f70b4
   array of matches, or NULL if there aren't any. */
Packit 8f70b4
static char **lftp_completion (const char *text,int start,int end)
Packit 8f70b4
{
Packit 8f70b4
   FileSetOutput fso;
Packit 8f70b4
Packit 8f70b4
   completion_shell->RestoreCWD();
Packit 8f70b4
Packit 8f70b4
   if(start>end)  // workaround for a bug in readline
Packit 8f70b4
      start=end;
Packit 8f70b4
Packit 8f70b4
   GlobURL *rg=0;
Packit 8f70b4
Packit 8f70b4
   rl_completion_append_character=' ';
Packit 8f70b4
   rl_ignore_some_completions_function=0;
Packit 8f70b4
   shell_cmd=false;
Packit 8f70b4
   quote_glob=false;
Packit 8f70b4
   quote_glob_basename=false;
Packit 8f70b4
   inhibit_tilde=false;
Packit 8f70b4
   delete glob_res;
Packit 8f70b4
   glob_res=0;
Packit 8f70b4
Packit 8f70b4
   completion_type type=cmd_completion_type(rl_line_buffer,start);
Packit 8f70b4
Packit 8f70b4
   len=end-start;
Packit 8f70b4
Packit 8f70b4
   char *(*generator)(const char *text,int state) = 0;
Packit 8f70b4
Packit 8f70b4
   switch(type)
Packit 8f70b4
   {
Packit 8f70b4
   case NO_COMPLETION:
Packit 8f70b4
      rl_attempted_completion_over = 1;
Packit 8f70b4
      return 0;
Packit 8f70b4
   case COMMAND:
Packit 8f70b4
      generator = command_generator;
Packit 8f70b4
      break;
Packit 8f70b4
   case BOOKMARK:
Packit 8f70b4
      bookmark_prepend_bm=false;
Packit 8f70b4
      generator = bookmark_generator;
Packit 8f70b4
      break;
Packit 8f70b4
   case STRING_ARRAY:
Packit 8f70b4
      generator = array_generator;
Packit 8f70b4
      break;
Packit 8f70b4
   case VARIABLE:
Packit 8f70b4
      generator = vars_generator;
Packit 8f70b4
      break;
Packit 8f70b4
Packit 8f70b4
      char *pat;
Packit 8f70b4
   case LOCAL:
Packit 8f70b4
   case LOCAL_DIR: {
Packit 8f70b4
      if(force_remote || (url::is_url(text) && remote_completion))
Packit 8f70b4
      {
Packit 8f70b4
	 if(type==LOCAL_DIR)
Packit 8f70b4
	    type=REMOTE_DIR;
Packit 8f70b4
	 goto really_remote;
Packit 8f70b4
      }
Packit 8f70b4
   really_local:
Packit 8f70b4
      fso.parse_res(ResMgr::Query("cmd:cls-completion-default", 0));
Packit 8f70b4
Packit 8f70b4
      bool tilde_expanded=false;
Packit 8f70b4
      const char *home=getenv("HOME");
Packit 8f70b4
      int home_len=xstrlen(home);
Packit 8f70b4
      pat=string_alloca((len+home_len)*2+10);
Packit 8f70b4
      if(len>0 && home_len>0 && text[0]=='~' && (len==1 || text[1]=='/'))
Packit 8f70b4
      {
Packit 8f70b4
	 glob_quote(pat,home,home_len);
Packit 8f70b4
	 glob_quote(pat+strlen(pat),text+1,len-1);
Packit 8f70b4
	 if(len==1)
Packit 8f70b4
	    strcat(pat,"/");
Packit 8f70b4
	 tilde_expanded=true;
Packit 8f70b4
	 inhibit_tilde=false;
Packit 8f70b4
      }
Packit 8f70b4
      else
Packit 8f70b4
      {
Packit 8f70b4
	 glob_quote(pat,text,len);
Packit 8f70b4
	 inhibit_tilde=true;
Packit 8f70b4
      }
Packit 8f70b4
Packit 8f70b4
      /* if we want case-insensitive matching, we need to match everything
Packit 8f70b4
       * in the dir and weed it ourselves (let the generator do it), since
Packit 8f70b4
       * glob() has no casefold option */
Packit 8f70b4
      if(fso.patterns_casefold) {
Packit 8f70b4
	 rl_variable_bind("completion-ignore-case", "1");
Packit 8f70b4
Packit 8f70b4
	 /* strip back to the last / */
Packit 8f70b4
	 char *sl = strrchr(pat, '/');
Packit 8f70b4
	 if(sl) *++sl = 0;
Packit 8f70b4
	 else pat[0] = 0;
Packit 8f70b4
      } else {
Packit 8f70b4
	 rl_variable_bind("completion-ignore-case", "0");
Packit 8f70b4
      }
Packit 8f70b4
Packit 8f70b4
      strcat(pat,"*");
Packit 8f70b4
Packit 8f70b4
      glob_t pglob;
Packit 8f70b4
      glob(pat,GLOB_PERIOD,NULL,&pglob);
Packit 8f70b4
      glob_res=new FileSet;
Packit 8f70b4
      for(int i=0; i<(int)pglob.gl_pathc; i++)
Packit 8f70b4
      {
Packit 8f70b4
	 char *src=pglob.gl_pathv[i];
Packit 8f70b4
	 if(tilde_expanded && home_len>0)
Packit 8f70b4
	 {
Packit 8f70b4
	    src+=home_len-1;
Packit 8f70b4
	    *src='~';
Packit 8f70b4
	 }
Packit 8f70b4
	 if(!strcmp(basename_ptr(src), ".")) continue;
Packit 8f70b4
	 if(!strcmp(basename_ptr(src), "..")) continue;
Packit 8f70b4
	 if(type==LOCAL_DIR && not_dir(src)) continue;
Packit 8f70b4
Packit 8f70b4
	 FileInfo *f = new FileInfo;
Packit 8f70b4
Packit 8f70b4
	 f->LocalFile(src, false);
Packit 8f70b4
	 glob_res->Add(f);
Packit 8f70b4
      }
Packit 8f70b4
      globfree(&pglob);
Packit 8f70b4
Packit 8f70b4
      rl_filename_completion_desired=1;
Packit 8f70b4
      generator = file_generator;
Packit 8f70b4
      break;
Packit 8f70b4
   }
Packit 8f70b4
   case REMOTE_FILE:
Packit 8f70b4
   case REMOTE_DIR: {
Packit 8f70b4
      if(!remote_completion && !force_remote)
Packit 8f70b4
      {
Packit 8f70b4
	 if(type==REMOTE_DIR)
Packit 8f70b4
	    type=LOCAL_DIR;
Packit 8f70b4
	 goto really_local;
Packit 8f70b4
      }
Packit 8f70b4
   really_remote:
Packit 8f70b4
      if(!strncmp(text,"bm:",3) && !strchr(text,'/'))
Packit 8f70b4
      {
Packit 8f70b4
	 bookmark_prepend_bm=true;
Packit 8f70b4
	 generator=bookmark_generator;
Packit 8f70b4
	 rl_completion_append_character='/';
Packit 8f70b4
	 break;
Packit 8f70b4
      }
Packit 8f70b4
Packit 8f70b4
      pat=string_alloca(len*2+10);
Packit 8f70b4
      glob_quote(pat,text,len);
Packit 8f70b4
Packit 8f70b4
      if(pat[0]=='~' && pat[1]==0)
Packit 8f70b4
	 strcat(pat,"/");
Packit 8f70b4
Packit 8f70b4
      if(pat[0]=='~' && pat[1]=='/')
Packit 8f70b4
	 inhibit_tilde=false;
Packit 8f70b4
      else
Packit 8f70b4
	 inhibit_tilde=true;
Packit 8f70b4
      strcat(pat,"*");
Packit 8f70b4
Packit 8f70b4
      completion_shell->session->DontSleep();
Packit 8f70b4
Packit 8f70b4
      SignalHook::ResetCount(SIGINT);
Packit 8f70b4
      glob_res=NULL;
Packit 8f70b4
      rg=new GlobURL(completion_shell->session,pat,
Packit 8f70b4
		     type==REMOTE_DIR?GlobURL::DIRS_ONLY:GlobURL::ALL);
Packit 8f70b4
Packit 8f70b4
      rl_save_prompt();
Packit 8f70b4
Packit 8f70b4
      fso.parse_res(ResMgr::Query("cmd:cls-completion-default", 0));
Packit 8f70b4
Packit 8f70b4
      if(rg)
Packit 8f70b4
      {
Packit 8f70b4
	 rg->NoInhibitTilde();
Packit 8f70b4
	 if(fso.patterns_casefold) {
Packit 8f70b4
	    rl_variable_bind("completion-ignore-case", "1");
Packit 8f70b4
	    rg->CaseFold();
Packit 8f70b4
	 } else
Packit 8f70b4
	    rl_variable_bind("completion-ignore-case", "0");
Packit 8f70b4
Packit 8f70b4
	 Timer status_timer;
Packit 8f70b4
	 status_timer.SetMilliSeconds(20);
Packit 8f70b4
Packit 8f70b4
	 for(;;)
Packit 8f70b4
	 {
Packit 8f70b4
	    SMTask::Schedule();
Packit 8f70b4
	    if(rg->Done())
Packit 8f70b4
	       break;
Packit 8f70b4
	    if(SignalHook::GetCount(SIGINT))
Packit 8f70b4
	    {
Packit 8f70b4
	       SignalHook::ResetCount(SIGINT);
Packit 8f70b4
	       rl_attempted_completion_over = 1;
Packit 8f70b4
	       delete rg;
Packit 8f70b4
Packit 8f70b4
	       rl_restore_prompt();
Packit 8f70b4
	       rl_clear_message();
Packit 8f70b4
Packit 8f70b4
	       return 0;
Packit 8f70b4
	    }
Packit 8f70b4
Packit 8f70b4
	    if(!fso.quiet)
Packit 8f70b4
	    {
Packit 8f70b4
	       /* don't set blank status; if we're operating from cache,
Packit 8f70b4
		* that's all we'll get and it'll look ugly: */
Packit 8f70b4
	       const char *ret = rg->Status();
Packit 8f70b4
	       if(*ret)
Packit 8f70b4
	       {
Packit 8f70b4
		  if(status_timer.Stopped())
Packit 8f70b4
		  {
Packit 8f70b4
		     rl_message ("%s> ", ret);
Packit 8f70b4
		     status_timer.SetResource("cmd:status-interval",0);
Packit 8f70b4
		  }
Packit 8f70b4
	       }
Packit 8f70b4
	    }
Packit 8f70b4
Packit 8f70b4
	    SMTask::Block();
Packit 8f70b4
	 }
Packit 8f70b4
	 glob_res=new FileSet(rg->GetResult());
Packit 8f70b4
	 glob_res->rewind();
Packit 8f70b4
      }
Packit 8f70b4
      rl_restore_prompt();
Packit 8f70b4
      rl_clear_message();
Packit 8f70b4
Packit 8f70b4
      if(glob_res->get_fnum()==1)
Packit 8f70b4
      {
Packit 8f70b4
	 FileInfo *info=glob_res->curr();
Packit 8f70b4
	 rl_completion_append_character=' ';
Packit 8f70b4
	 if(info->defined&info->TYPE && info->filetype==info->DIRECTORY)
Packit 8f70b4
	    rl_completion_append_character='/';
Packit 8f70b4
      }
Packit 8f70b4
      rl_filename_completion_desired=1;
Packit 8f70b4
      generator = file_generator;
Packit 8f70b4
      break;
Packit 8f70b4
   }
Packit 8f70b4
   } /* end switch */
Packit 8f70b4
Packit 8f70b4
   assert(generator);
Packit 8f70b4
Packit 8f70b4
   char quoted=((lftp_char_is_quoted(rl_line_buffer,start) &&
Packit 8f70b4
		 strchr(rl_completer_quote_characters,rl_line_buffer[start-1]))
Packit 8f70b4
		? rl_line_buffer[start-1] : 0);
Packit 8f70b4
   xstring_ca textq(bash_dequote_filename(text, quoted));
Packit 8f70b4
   len=strlen(textq);
Packit 8f70b4
Packit 8f70b4
   char **matches=lftp_rl_completion_matches(textq,generator);
Packit 8f70b4
Packit 8f70b4
   if(rg)
Packit 8f70b4
      delete rg;
Packit 8f70b4
Packit 8f70b4
   if(vars)
Packit 8f70b4
   {
Packit 8f70b4
      // delete vars?
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
   if(!matches)
Packit 8f70b4
   {
Packit 8f70b4
      rl_attempted_completion_over = 1;
Packit 8f70b4
      return 0;
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
   if(type==REMOTE_DIR)
Packit 8f70b4
      rl_completion_append_character='/';
Packit 8f70b4
Packit 8f70b4
   return matches;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
extern "C" void lftp_line_complete()
Packit 8f70b4
{
Packit 8f70b4
   delete glob_res;
Packit 8f70b4
   glob_res=0;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
enum { COMPLETE_DQUOTE,COMPLETE_SQUOTE,COMPLETE_BSQUOTE };
Packit 8f70b4
#define completion_quoting_style COMPLETE_BSQUOTE
Packit 8f70b4
Packit 8f70b4
/* **************************************************************** */
Packit 8f70b4
/*								    */
Packit 8f70b4
/*	 Functions for quoting strings to be re-read as input	    */
Packit 8f70b4
/*								    */
Packit 8f70b4
/* **************************************************************** */
Packit 8f70b4
Packit 8f70b4
/* Return a new string which is the single-quoted version of STRING.
Packit 8f70b4
   Used by alias and trap, among others. */
Packit 8f70b4
static char *
Packit 8f70b4
single_quote (char *string)
Packit 8f70b4
{
Packit 8f70b4
  int c;
Packit 8f70b4
  char *result, *r, *s;
Packit 8f70b4
Packit 8f70b4
  result = (char *)xmalloc (3 + (4 * strlen (string)));
Packit 8f70b4
  r = result;
Packit 8f70b4
  *r++ = '\'';
Packit 8f70b4
Packit 8f70b4
  for (s = string; s && (c = *s); s++)
Packit 8f70b4
    {
Packit 8f70b4
      *r++ = c;
Packit 8f70b4
Packit 8f70b4
      if (c == '\'')
Packit 8f70b4
	{
Packit 8f70b4
	  *r++ = '\\';	/* insert escaped single quote */
Packit 8f70b4
	  *r++ = '\'';
Packit 8f70b4
	  *r++ = '\'';	/* start new quoted string */
Packit 8f70b4
	}
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
  *r++ = '\'';
Packit 8f70b4
  *r = '\0';
Packit 8f70b4
Packit 8f70b4
  return (result);
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
/* Quote STRING using double quotes.  Return a new string. */
Packit 8f70b4
static char *
Packit 8f70b4
double_quote (char *string)
Packit 8f70b4
{
Packit 8f70b4
  int c;
Packit 8f70b4
  char *result, *r, *s;
Packit 8f70b4
Packit 8f70b4
  result = (char *)xmalloc (3 + (2 * strlen (string)));
Packit 8f70b4
  r = result;
Packit 8f70b4
  *r++ = '"';
Packit 8f70b4
Packit 8f70b4
  for (s = string; s && (c = *s); s++)
Packit 8f70b4
    {
Packit 8f70b4
      switch (c)
Packit 8f70b4
        {
Packit 8f70b4
	case '$':
Packit 8f70b4
	case '`':
Packit 8f70b4
	  if(!shell_cmd)
Packit 8f70b4
	     goto def;
Packit 8f70b4
	case '"':
Packit 8f70b4
	case '\\':
Packit 8f70b4
	  *r++ = '\\';
Packit 8f70b4
	default: def:
Packit 8f70b4
	  *r++ = c;
Packit 8f70b4
	  break;
Packit 8f70b4
        }
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
  *r++ = '"';
Packit 8f70b4
  *r = '\0';
Packit 8f70b4
Packit 8f70b4
  return (result);
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
/* Quote special characters in STRING using backslashes.  Return a new
Packit 8f70b4
   string. */
Packit 8f70b4
static char *
Packit 8f70b4
backslash_quote (char *string)
Packit 8f70b4
{
Packit 8f70b4
  int c;
Packit 8f70b4
  char *result, *r, *s;
Packit 8f70b4
  char *bn = basename_ptr(string);
Packit 8f70b4
Packit 8f70b4
  result = (char*)xmalloc (2 * strlen (string) + 1);
Packit 8f70b4
Packit 8f70b4
  for (r = result, s = string; s && (c = *s); s++)
Packit 8f70b4
    {
Packit 8f70b4
      switch (c)
Packit 8f70b4
	{
Packit 8f70b4
	case '(': case ')':
Packit 8f70b4
	case '{': case '}':			/* reserved words */
Packit 8f70b4
	case '^':
Packit 8f70b4
	case '$': case '`':			/* expansion chars */
Packit 8f70b4
	  if(!shell_cmd)
Packit 8f70b4
	    goto def;
Packit 8f70b4
	case '*': case '[': case '?': case ']':	/* globbing chars */
Packit 8f70b4
	  if(!shell_cmd && !quote_glob && (!quote_glob_basename || s
Packit 8f70b4
	    goto def;
Packit 8f70b4
	  /*fallthrough*/
Packit 8f70b4
	case ' ': case '\t': case '\n':		/* IFS white space */
Packit 8f70b4
	case '"': case '\'': case '\\':		/* quoting chars */
Packit 8f70b4
	case '|': case '&': case ';':		/* shell metacharacters */
Packit 8f70b4
	case '<': case '>': case '!':
Packit 8f70b4
	  *r++ = '\\';
Packit 8f70b4
	  *r++ = c;
Packit 8f70b4
	  break;
Packit 8f70b4
	case '~':				/* tilde expansion */
Packit 8f70b4
	  if (s == string && inhibit_tilde)
Packit 8f70b4
	    *r++ = '.', *r++ = '/';
Packit 8f70b4
	  goto def;
Packit 8f70b4
	case '#':				/* comment char */
Packit 8f70b4
	  if(!shell_cmd)
Packit 8f70b4
	    goto def;
Packit 8f70b4
	  if (s == string)
Packit 8f70b4
	    *r++ = '\\';
Packit 8f70b4
	  /* FALLTHROUGH */
Packit 8f70b4
	default: def:
Packit 8f70b4
	  *r++ = c;
Packit 8f70b4
	  break;
Packit 8f70b4
	}
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
  *r = '\0';
Packit 8f70b4
  return (result);
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
#if 0 // no need yet
Packit 8f70b4
static int
Packit 8f70b4
contains_shell_metas (char *string)
Packit 8f70b4
{
Packit 8f70b4
  char *s;
Packit 8f70b4
Packit 8f70b4
  for (s = string; s && *s; s++)
Packit 8f70b4
    {
Packit 8f70b4
      switch (*s)
Packit 8f70b4
	{
Packit 8f70b4
	case ' ': case '\t': case '\n':		/* IFS white space */
Packit 8f70b4
	case '\'': case '"': case '\\':		/* quoting chars */
Packit 8f70b4
	case '|': case '&': case ';':		/* shell metacharacters */
Packit 8f70b4
	case '(': case ')': case '<': case '>':
Packit 8f70b4
	case '!': case '{': case '}':		/* reserved words */
Packit 8f70b4
	case '*': case '[': case '?': case ']':	/* globbing chars */
Packit 8f70b4
	case '^':
Packit 8f70b4
	case '$': case '`':			/* expansion chars */
Packit 8f70b4
	  return (1);
Packit 8f70b4
	case '#':
Packit 8f70b4
	  if (s == string)			/* comment char */
Packit 8f70b4
	    return (1);
Packit 8f70b4
	  /* FALLTHROUGH */
Packit 8f70b4
	default:
Packit 8f70b4
	  break;
Packit 8f70b4
	}
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
  return (0);
Packit 8f70b4
}
Packit 8f70b4
#endif //0
Packit 8f70b4
Packit 8f70b4
/* Filename quoting for completion. */
Packit 8f70b4
/* A function to strip quotes that are not protected by backquotes.  It
Packit 8f70b4
   allows single quotes to appear within double quotes, and vice versa.
Packit 8f70b4
   It should be smarter. */
Packit 8f70b4
static char *
Packit 8f70b4
bash_dequote_filename (const char *text, int quote_char)
Packit 8f70b4
{
Packit 8f70b4
  char *ret;
Packit 8f70b4
  const char *p;
Packit 8f70b4
  char *r;
Packit 8f70b4
  int l, quoted;
Packit 8f70b4
Packit 8f70b4
  l = strlen (text);
Packit 8f70b4
  ret = (char*)xmalloc (l + 1);
Packit 8f70b4
  for (quoted = quote_char, p = text, r = ret; p && *p; p++)
Packit 8f70b4
    {
Packit 8f70b4
      /* Allow backslash-quoted characters to pass through unscathed. */
Packit 8f70b4
      if (*p == '\\')
Packit 8f70b4
	{
Packit 8f70b4
	  *r++ = *++p;
Packit 8f70b4
	  if (*p == '\0')
Packit 8f70b4
	    break;
Packit 8f70b4
	  continue;
Packit 8f70b4
	}
Packit 8f70b4
      /* Close quote. */
Packit 8f70b4
      if (quoted && *p == quoted)
Packit 8f70b4
        {
Packit 8f70b4
          quoted = 0;
Packit 8f70b4
          continue;
Packit 8f70b4
        }
Packit 8f70b4
      /* Open quote. */
Packit 8f70b4
      if (quoted == 0 && (*p == '\'' || *p == '"'))
Packit 8f70b4
        {
Packit 8f70b4
          quoted = *p;
Packit 8f70b4
          continue;
Packit 8f70b4
        }
Packit 8f70b4
      *r++ = *p;
Packit 8f70b4
    }
Packit 8f70b4
  *r = '\0';
Packit 8f70b4
  return ret;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
/* Quote characters that the readline completion code would treat as
Packit 8f70b4
   word break characters with backslashes.  Pass backslash-quoted
Packit 8f70b4
   characters through without examination. */
Packit 8f70b4
static char *
Packit 8f70b4
quote_word_break_chars (char *text)
Packit 8f70b4
{
Packit 8f70b4
  char *ret, *r, *s;
Packit 8f70b4
  int l;
Packit 8f70b4
Packit 8f70b4
  l = strlen (text);
Packit 8f70b4
  ret = (char*)xmalloc ((2 * l) + 1);
Packit 8f70b4
  for (s = text, r = ret; *s; s++)
Packit 8f70b4
    {
Packit 8f70b4
      /* Pass backslash-quoted characters through, including the backslash. */
Packit 8f70b4
      if (*s == '\\')
Packit 8f70b4
	{
Packit 8f70b4
	  *r++ = '\\';
Packit 8f70b4
	  *r++ = *++s;
Packit 8f70b4
	  if (*s == '\0')
Packit 8f70b4
	    break;
Packit 8f70b4
	  continue;
Packit 8f70b4
	}
Packit 8f70b4
      /* OK, we have an unquoted character.  Check its presence in
Packit 8f70b4
	 rl_completer_word_break_characters. */
Packit 8f70b4
      if (strchr (rl_completer_word_break_characters, *s))
Packit 8f70b4
        *r++ = '\\';
Packit 8f70b4
      *r++ = *s;
Packit 8f70b4
    }
Packit 8f70b4
  *r = '\0';
Packit 8f70b4
  return ret;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
/* Quote a filename using double quotes, single quotes, or backslashes
Packit 8f70b4
   depending on the value of completion_quoting_style.  If we're
Packit 8f70b4
   completing using backslashes, we need to quote some additional
Packit 8f70b4
   characters (those that readline treats as word breaks), so we call
Packit 8f70b4
   quote_word_break_chars on the result. */
Packit 8f70b4
static char *
Packit 8f70b4
bash_quote_filename (char *s, int rtype, char *qcp)
Packit 8f70b4
{
Packit 8f70b4
  char *rtext, *mtext, *ret;
Packit 8f70b4
  int rlen, cs;
Packit 8f70b4
Packit 8f70b4
  rtext = (char *)NULL;
Packit 8f70b4
Packit 8f70b4
  /* If RTYPE == MULT_MATCH, it means that there is
Packit 8f70b4
     more than one match.  In this case, we do not add
Packit 8f70b4
     the closing quote or attempt to perform tilde
Packit 8f70b4
     expansion.  If RTYPE == SINGLE_MATCH, we try
Packit 8f70b4
     to perform tilde expansion, because single and double
Packit 8f70b4
     quotes inhibit tilde expansion by the shell. */
Packit 8f70b4
Packit 8f70b4
  mtext = s;
Packit 8f70b4
#if 0
Packit 8f70b4
  if (mtext[0] == '~' && rtype == SINGLE_MATCH)
Packit 8f70b4
    mtext = bash_tilde_expand (s);
Packit 8f70b4
#endif
Packit 8f70b4
Packit 8f70b4
  cs = completion_quoting_style;
Packit 8f70b4
  /* Might need to modify the default completion style based on *qcp,
Packit 8f70b4
     since it's set to any user-provided opening quote. */
Packit 8f70b4
  if (*qcp == '"')
Packit 8f70b4
    cs = COMPLETE_DQUOTE;
Packit 8f70b4
  else if (*qcp == '\'')
Packit 8f70b4
    cs = COMPLETE_SQUOTE;
Packit 8f70b4
#if defined (BANG_HISTORY)
Packit 8f70b4
  else if (*qcp == '\0' && history_expansion && cs == COMPLETE_DQUOTE &&
Packit 8f70b4
	   history_expansion_inhibited == 0 && strchr (mtext, '!'))
Packit 8f70b4
    cs = COMPLETE_BSQUOTE;
Packit 8f70b4
Packit 8f70b4
  if (*qcp == '"' && history_expansion && cs == COMPLETE_DQUOTE &&
Packit 8f70b4
        history_expansion_inhibited == 0 && strchr (mtext, '!'))
Packit 8f70b4
    {
Packit 8f70b4
      cs = COMPLETE_BSQUOTE;
Packit 8f70b4
      *qcp = '\0';
Packit 8f70b4
    }
Packit 8f70b4
#endif
Packit 8f70b4
Packit 8f70b4
  switch (cs)
Packit 8f70b4
    {
Packit 8f70b4
    case COMPLETE_DQUOTE:
Packit 8f70b4
      rtext = double_quote (mtext);
Packit 8f70b4
      break;
Packit 8f70b4
    case COMPLETE_SQUOTE:
Packit 8f70b4
      rtext = single_quote (mtext);
Packit 8f70b4
      break;
Packit 8f70b4
    case COMPLETE_BSQUOTE:
Packit 8f70b4
      rtext = backslash_quote (mtext);
Packit 8f70b4
      break;
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
  if (mtext != s)
Packit 8f70b4
    free (mtext);
Packit 8f70b4
Packit 8f70b4
  /* We may need to quote additional characters: those that readline treats
Packit 8f70b4
     as word breaks that are not quoted by backslash_quote. */
Packit 8f70b4
  if (rtext && cs == COMPLETE_BSQUOTE)
Packit 8f70b4
    {
Packit 8f70b4
      mtext = quote_word_break_chars (rtext);
Packit 8f70b4
      free (rtext);
Packit 8f70b4
      rtext = mtext;
Packit 8f70b4
    }
Packit 8f70b4
Packit 8f70b4
  /* Leave the opening quote intact.  The readline completion code takes
Packit 8f70b4
     care of avoiding doubled opening quotes. */
Packit 8f70b4
  rlen = strlen (rtext);
Packit 8f70b4
  ret = (char*)xmalloc (rlen + 1);
Packit 8f70b4
  strcpy (ret, rtext);
Packit 8f70b4
Packit 8f70b4
  /* If there are multiple matches, cut off the closing quote. */
Packit 8f70b4
  if (rtype == MULT_MATCH && cs != COMPLETE_BSQUOTE)
Packit 8f70b4
    ret[rlen - 1] = '\0';
Packit 8f70b4
  free (rtext);
Packit 8f70b4
  return ret;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
static int skip_quoted(const char *s, int i, char q)
Packit 8f70b4
{
Packit 8f70b4
   while(s[i] && s[i]!=q)
Packit 8f70b4
   {
Packit 8f70b4
      if(s[i]=='\\' && s[i+1])
Packit 8f70b4
	 i++;
Packit 8f70b4
      i++;
Packit 8f70b4
   }
Packit 8f70b4
   if(s[i])
Packit 8f70b4
      i++;
Packit 8f70b4
   return i;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
int lftp_char_is_quoted(const char *string,int eindex)
Packit 8f70b4
{
Packit 8f70b4
  int i, pass_next;
Packit 8f70b4
Packit 8f70b4
  for (i = pass_next = 0; i <= eindex; i++)
Packit 8f70b4
    {
Packit 8f70b4
      if (pass_next)
Packit 8f70b4
        {
Packit 8f70b4
          pass_next = 0;
Packit 8f70b4
          if (i >= eindex)
Packit 8f70b4
            return 1;
Packit 8f70b4
          continue;
Packit 8f70b4
        }
Packit 8f70b4
      else if (string[i] == '"' || string[i] == '\'')
Packit 8f70b4
        {
Packit 8f70b4
	  char quote = string[i];
Packit 8f70b4
          i = skip_quoted (string, ++i, quote);
Packit 8f70b4
          if (i > eindex)
Packit 8f70b4
            return 1;
Packit 8f70b4
          i--;  /* the skip functions increment past the closing quote. */
Packit 8f70b4
        }
Packit 8f70b4
      else if (string[i] == '\\')
Packit 8f70b4
        {
Packit 8f70b4
          pass_next = 1;
Packit 8f70b4
          continue;
Packit 8f70b4
        }
Packit 8f70b4
    }
Packit 8f70b4
  return (0);
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
extern "C" int (*rl_last_func)(int,int);
Packit 8f70b4
static int lftp_complete_remote(int count,int key)
Packit 8f70b4
{
Packit 8f70b4
   if(rl_last_func == lftp_complete_remote)
Packit 8f70b4
      rl_last_func = rl_complete;
Packit 8f70b4
Packit 8f70b4
   force_remote = true;
Packit 8f70b4
   int ret=rl_complete(count,key);
Packit 8f70b4
   force_remote = false;
Packit 8f70b4
   return ret;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
int lftp_rl_getc(FILE *file)
Packit 8f70b4
{
Packit 8f70b4
   SignalHook::DoCount(SIGINT);
Packit 8f70b4
   SMTaskRef<CharReader> rr(new CharReader(fileno(file)));
Packit 8f70b4
   CharReader& r=*rr.get_non_const();
Packit 8f70b4
   for(;;)
Packit 8f70b4
   {
Packit 8f70b4
      SMTask::Schedule();
Packit 8f70b4
      int res=r.GetChar();
Packit 8f70b4
      if(res==r.EOFCHAR)
Packit 8f70b4
	 return EOF;
Packit 8f70b4
      if(res!=r.NOCHAR)
Packit 8f70b4
	 return res;
Packit 8f70b4
      lftp_rl_redisplay_maybe();
Packit 8f70b4
      SMTask::Block();
Packit 8f70b4
      if(SignalHook::GetCount(SIGINT)>0)
Packit 8f70b4
      {
Packit 8f70b4
	 if(rl_line_buffer && rl_end>0)
Packit 8f70b4
	    rl_kill_full_line(0,0);
Packit 8f70b4
	 return '\n';
Packit 8f70b4
      }
Packit 8f70b4
   }
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
extern int lftp_slot(int,int);
Packit 8f70b4
Packit 8f70b4
/* Tell the GNU Readline library how to complete.  We want to try to complete
Packit 8f70b4
   on command names if this is the first word in the line, or on filenames
Packit 8f70b4
   if not. */
Packit 8f70b4
void lftp_readline_init ()
Packit 8f70b4
{
Packit 8f70b4
   lftp_rl_init(
Packit 8f70b4
      "lftp",		      // rl_readline_name
Packit 8f70b4
      lftp_completion,	      // rl_attempted_completion_function
Packit 8f70b4
      lftp_rl_getc,	      // rl_getc_function
Packit 8f70b4
      "\"'",		      // rl_completer_quote_characters
Packit 8f70b4
      " \t\n\"'",	      // rl_completer_word_break_characters
Packit 8f70b4
      " \t\n\\\"'>;|&()*?[]~!",// rl_filename_quote_characters
Packit 8f70b4
      bash_quote_filename,    // rl_filename_quoting_function
Packit 8f70b4
      bash_dequote_filename,  // rl_filename_dequoting_function
Packit 8f70b4
      lftp_char_is_quoted);   // rl_char_is_quoted_p
Packit 8f70b4
Packit 8f70b4
   lftp_rl_add_defun("complete-remote",lftp_complete_remote,-1);
Packit 8f70b4
   lftp_rl_bind("Meta-Tab","complete-remote");
Packit 8f70b4
Packit 8f70b4
   lftp_rl_add_defun("slot-change",lftp_slot,-1);
Packit 8f70b4
   char key[7];
Packit 8f70b4
   strcpy(key,"Meta-N");
Packit 8f70b4
   for(int i=0; i<10; i++)
Packit 8f70b4
   {
Packit 8f70b4
      key[5]='0'+i;
Packit 8f70b4
      lftp_rl_bind(key,"slot-change");
Packit 8f70b4
   }
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
extern "C" void completion_display_list (char **matches, int len)
Packit 8f70b4
{
Packit 8f70b4
   JobRef<OutputJob> b(new OutputJob((FDStream *) NULL, "completion"));
Packit 8f70b4
Packit 8f70b4
   if(glob_res) {
Packit 8f70b4
      /* Our last completion action was of files, and we kept that
Packit 8f70b4
       * data around.  Take the files in glob_res which are in matches
Packit 8f70b4
       * and put them in another FileSet.  (This is a little wasteful,
Packit 8f70b4
       * since we're going to use it briefly and discard it, but it's
Packit 8f70b4
       * not worth adding temporary-filtering options to FileSet.) */
Packit 8f70b4
      FileSet tmp;
Packit 8f70b4
      for(int i = 1; i <= len; i++) {
Packit 8f70b4
	 FileInfo *fi = glob_res->FindByName(matches[i]);
Packit 8f70b4
	 assert(fi);
Packit 8f70b4
	 tmp.Add(new FileInfo(*fi));
Packit 8f70b4
      }
Packit 8f70b4
Packit 8f70b4
      FileSetOutput fso;
Packit 8f70b4
      fso.config(b);
Packit 8f70b4
Packit 8f70b4
      fso.parse_res(ResMgr::Query("cmd:cls-completion-default", 0));
Packit 8f70b4
Packit 8f70b4
      fso.print(tmp, b);
Packit 8f70b4
   } else {
Packit 8f70b4
      /* Just pass it through ColumnInfo. */
Packit 8f70b4
      ColumnOutput c;
Packit 8f70b4
      for(int i = 1; i <= len; i++) {
Packit 8f70b4
	 c.append();
Packit 8f70b4
	 c.add(matches[i], "");
Packit 8f70b4
      }
Packit 8f70b4
      c.print(b, b->GetWidth(), b->IsTTY());
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
   b->PutEOF();
Packit 8f70b4
Packit 8f70b4
   while(!b->Done())
Packit 8f70b4
   {
Packit 8f70b4
      SMTask::Schedule();
Packit 8f70b4
      if(SignalHook::GetCount(SIGINT))
Packit 8f70b4
      {
Packit 8f70b4
	 SignalHook::ResetCount(SIGINT);
Packit 8f70b4
	 break;
Packit 8f70b4
      }
Packit 8f70b4
   }
Packit 8f70b4
}