Advertisement
Guest User

KingBash

a guest
Jan 22nd, 2013
71
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 47.65 KB | None | 0 0
  1. #! /usr/bin/env python3
  2. import os, os.path, re, stat, termios, fcntl, sys, string, operator, struct, gc, time
  3. from optparse import OptionParser
  4. from unicodedata import east_asian_width
  5. from subprocess import *
  6.  
  7. def usage_help_text():
  8.     print("""Usage
  9. kingbash relies on the environment variables READLINE_LINE and READLINE_POINT which are automatically set by bind -x.
  10. When READLINE_POINT is not set, it is presumed 0.
  11. One can test functionality of tab completion by setting READLINE_LINE (and optionally READLINE_POINT to the length of READLINE_LINE) with export and calling kingbash without arguments.
  12. When completing a command instead of a file, extra commands (functions, aliases, builtins) and files (so far only bash variables starting with $) can be supplied with --extracommands and --extrafiles respectively as one large string with items delimited by newlines.
  13.  
  14. history completion can be tested by piping data to kingbash, or using the option -r FILE
  15. No READLINE_X variables need to be set in this case.
  16. Note that by piping data to kingbash, the program will NOT return a value to be interpreted as a new READLINE_POINT so any bind -x script will have to use -r FILE
  17.  
  18. Special options:
  19. --forceheight N : make the screen N character instead of 1/3 of the terminal
  20. --slowhistory : prefer the older history style. Recommended with few items.
  21. --noretab-(az|backspace|directory) : don't complete after X
  22. --plustoall : allows * in tab completion and + will complete all files. Allows you to type a later part of the filename.
  23. --historytabcompletion : Remove the TAB binding from TAB/Insert's Copy selected item to the prompt, and make it work like TAB completion.
  24. --uniquereversesort : sort the file loaded with -r FILE in reverse without duplicate entries
  25. --bashcompletion : use command arguments from /etc/bash_completion.d/
  26. --wraplonglines : wrap mode
  27. --bashcompletionexception : for use with undesirable bash completions commands, space delimited. By default does only mplayer.
  28. --atime : sort by access time
  29. --mtime : sort by modification time
  30. --reverse : reverse the sort
  31.  
  32. Tab Completion:
  33.  
  34. function kingbash.fn() {
  35.  echo -n "KingBash> $READLINE_LINE" #Where "KingBash> " looks best if it resembles your PS1, at least in length.
  36.  OUTPUT=`"""+os.path.split(sys.argv[0])[-1]+""" --bashcompletion --plustoall --noretab-backspace --extracommands "$(compgen -ab -A function)" --extrafiles "$(compgen -v -P $)"`
  37.  READLINE_POINT=`echo "$OUTPUT" | tail -n 1`
  38.  READLINE_LINE=`echo "$OUTPUT" | head -n -1`
  39.  echo -ne "\\r\\e[2K"; }
  40. [[ $- =~ i ]] && bind -x '"\\t":kingbash.fn'
  41.  
  42. Alternative with neater long line handling:
  43.  
  44. PS1+='\[\e[s\]'
  45.  
  46. function kingbash.fn() {
  47.  local KBCOUNT OUTPUT KBMOVE KBLINES
  48.  for ((unused=0; unused<20; unused++)); do read -n 1 -t 0.000000001; done #clear input buffer
  49.  echo -ne '\e[u\e[6n'
  50.  read -sdR
  51.  [[ $REPLY =~ ..([0-9]*).([0-9]*) ]]
  52.  (( ${BASH_REMATCH[1]} == LINES )) && KBMOVE=A || KBMOVE=B
  53.  KBLINES=$(((${BASH_REMATCH[2]} + ${#READLINE_LINE} ) / COLUMNS))
  54.  for ((KBCOUNT=0;KBCOUNT < $KBLINES; KBCOUNT++)); do
  55.    echo -ne '\r\e[2K\e['$KBMOVE
  56.  done
  57.  if [[ $KBLINES != 0 && $KBMOVE == B ]]; then
  58.    echo -ne "\e[$KBLINES"A
  59.  fi
  60.  echo -ne "${READLINE_LINE:0:$((COLUMNS-${BASH_REMATCH[2]}))}"
  61.  OUTPUT=$("""+os.path.split(sys.argv[0])[-1]+""" --bashcompletion --plustoall --noretab-backspace --extracommands "$(compgen -ab -A function)" --extrafiles "$(compgen -v -P $)")
  62.  READLINE_POINT=$(echo "$OUTPUT"|tail -n1)
  63.  READLINE_LINE=$(echo "$OUTPUT"|head -n -1)
  64.  echo -ne '\r\e[2K'
  65.  [[ $PS1 =~ \\n ]]
  66.  for (( KBCOUNT=0; KBCOUNT < ${#BASH_REMATCH}; KBCOUNT++ )); do
  67.    echo -ne '\e[A\r\e[2K'
  68.  done
  69. }
  70. bind -x '"\t":kingbash.fn'
  71.  
  72.  
  73. History search:
  74.  
  75. function kingbash.hs() {
  76.  old_line=$READLINE_LINE
  77.  echo -n "KingBash> $READLINE_LINE"
  78.  history -a
  79.  OUTPUT=`"""+os.path.split(sys.argv[0])[-1]+""" -r <(tac $HISTFILE)`
  80. #Alternatively, for unique entries:
  81. #  OUTPUT=`"""+os.path.split(sys.argv[0])[-1]+""" -r <(tac $HISTFILE | cat -n - | sort -u -k 2 | sort -n | cut -f2-)`
  82.  READLINE_POINT=`echo "$OUTPUT" | tail -n 1`
  83.  READLINE_LINE=`echo "$OUTPUT" | head -n -1`
  84.  echo -ne "\\r\\e[2K"
  85.  #Press Enter Automatically:
  86.  #[ "$old_line" != "$READLINE_LINE" ] && { sleep 0.1; xdotool key --clearmodifiers Return; }
  87. }
  88. [[ $- =~ i ]] && bind -x '"\\x12":kingbash.hs'
  89.  
  90.  
  91. CLI dmenu:
  92.  
  93. ... | """+os.path.split(sys.argv[0])[-1]+"""
  94.  
  95.  
  96. GUI dmenu:
  97.  
  98. rm /tmp/menu
  99. cat > /tmp/inmenu
  100. urxvt -e bash -c 'export READLINE_POINT=0; answ=$(""" + os.path.split(sys.argv[0])[-1] + """ -r /tmp/inmenu | head -n 1); echo -n "$answ" > /tmp/menu'
  101. while sleep 0.1; do [ -f /tmp/menu ] && break; done
  102. cat /tmp/menu
  103. rm /tmp/menu
  104. rm /tmp/inmenu
  105. -------
  106.  
  107. F1 inside the application will print a key table""")
  108.  
  109. def F1_help_text():
  110.     sys.stderr.write("""Get usage advice by running """+sys.argv[0]+""" without arguments
  111. -------------------------
  112. There are two modes: Tab completion and History/file/dmenu completion.
  113. ---Key table---
  114. +Both modes+
  115.  F1     This help
  116. Up/Down
  117. Shift+Up/Down
  118. Shift+Left/Right
  119. Alt+Left/Right
  120. Control+A
  121. Control+E
  122. Page up/Pagedown
  123.  Home/End   Move around the list
  124.  Enter      Complete the selection and quit
  125. Any other character is added "as is" (with the escape sequence replaced with ESC).
  126. Under "Tab completion" the program quits after a space or equals sign.
  127.  
  128. +Tab Completion+
  129.  F2     Erase first word and move cursor to start of line, after the selection has been completed
  130.  Left       Go to the parent directory and continue browsing
  131.  Right      Complete the selection, but if it's a directory, bring up the program again inside that directory
  132.  Escape     Quit, leaving the line as it is seen in the application
  133.  Backspace  Erase the character left of the cursor and complete again
  134.  Alt+Backspace  Same, but an entire word or path
  135.  Control+U  Clear the line and quit
  136.  Insert     Complete the selection, move the selector bar down, and continue as if we are completing the previous selection again
  137.  Asterisk   Add everything in the list to the command line. If there are 'suggestions', only complete those. Quit afterwards.
  138.  Tab        Quit, don't complete the filename but leave the path.
  139.  
  140. +History Completion+
  141.  Left       Move the cursor left
  142.  Right      Move the cursor right
  143.  Escape     Quit, restore the line to what it was when KingBash started
  144.  Backspace  Erase the character left of the cursor
  145.  Alt+Backspace  Same, but an entire word or path
  146. *Delete     Delete the character under the cursor
  147.  Control+U  Clear the line and continue
  148.  Tab        Make the current line the same as the selection
  149.  Insert     Like Tab
  150.  F2     Accept line as is. Like dmenu Shift+Enter (which is not possible to `read`).
  151.  
  152. * means this key does nothing at all in the other mode
  153. -------------------------
  154. """)
  155.  
  156. def main():
  157.     try:
  158.         fd=sys.stdin.fileno()
  159.         oldterm=termios.tcgetattr(fd)
  160.         dmenu_mode=False
  161.         del fd
  162.         del oldterm
  163.     except termios.error: #stdin is a pipe
  164.         dmenu_mode=True
  165.     try:
  166.         point=int(os.environ["READLINE_POINT"])
  167.     except:
  168.         if not dmenu_mode:
  169.             usage_help_text()
  170.             sys.exit(0)
  171.         else:
  172.             point=0
  173.     try:
  174.         line=os.environ["READLINE_LINE"]
  175.     except KeyError:
  176.         line=""
  177.     try:
  178.         point=len(line.encode("UTF8")[0:point].decode("UTF8"))
  179.     except:
  180.         pass
  181.     original_line=line
  182.     original_point=point
  183.  
  184.     parser=OptionParser()
  185.     parser.add_option("-r", "--readfile", action="store", type="string", dest="readfile", default=False,
  186.                 help="Instead of command or files, select from a custom list with entries in FILE")
  187.     parser.add_option("--extracommands", action="store", type="string", dest="extracommands", default="",
  188.                 help="Tab completion extra commands (like aliases, functions and builtins")
  189.     parser.add_option("--extrafiles", action="store", type="string", dest="extrafiles", default="",
  190.                 help="Tab completion extra files (like variables with compgen -v -P $")
  191.     parser.add_option("--rerun", action="store_true", dest="rerun", default=False,
  192.                 help="Let Kingbash know it is on a second run")
  193.     parser.add_option("--selected", action="store", type="int", dest="selected", default=0,
  194.                 help="Begin with highlighting this entry")
  195.     parser.add_option("--viewmin", action="store", type="int", dest="viewmin", default=0,
  196.                 help="Begin with this entry at page start")
  197.     parser.add_option("--slowhistory", action="store_true", dest="slowhistory", default=False,
  198.                 help="Prefer to calculate the history all at once instead of after scrolling")
  199.     parser.add_option("--forceheight", action="store", type="string", dest="forceheight", default="0",
  200.                 help="Force height of N (try $LINES) instead of a third of the screen")
  201.     parser.add_option("--loadpages", action="store", type="int", dest="loadpages", default=10,
  202.                 help="Amount of pages to load per update in dmenu/history mode for comfortable page down")
  203.     parser.add_option("--noretab-az", action="store_true", dest="noretab_az", default=False,
  204.                 help="After pressing a key, behave like regular tab completion and don't try to complete again")
  205.     parser.add_option("--noretab-backspace", action="store_true", dest="noretab_backspace", default=False,
  206.                 help="After pressing backspace, behave like regular tab completion and don't try to complete again")
  207.     parser.add_option("--noretab-directory", action="store_true", dest="noretab_directory", default=False,
  208.                 help="After pressing Left/Right, behave like regular tab completion and don't try to complete again")
  209.     parser.add_option("--plustoall", action="store_true", dest="plustoall", default=False,
  210.                 help="Allows * in tab completion. Will be interpreted as either \* when quitting, or glob when continuing to type, which allows you to search the completions more easily.")
  211.     parser.add_option("--historytabcompletion", action="store_true", dest="historytabcompletion", default=False,
  212.                 help="Allows TAB completion in history/dmenu mode. Insert will still copy the current selected item to the prompt as TAB did before.")
  213.     parser.add_option("--uniquereversesort", action="store_true", dest="uniquereversesort", default=False,
  214.                 help="Sort -r FILE first in reverse and then removing duplicates.")
  215.     parser.add_option("--bashcompletion", action="store_true", dest="bashcompletion", default=False,
  216.                 help="Run the line through /etc/bash_completions.d.")
  217.     parser.add_option("--bashcompletionexception", action="store", type="string", dest="bashcompletionexception", default="mplayer",
  218.                 help="Space delimited list of names of commands not to look up completions for.")
  219.     parser.add_option("--wraplonglines", action="store_true", dest="wraplonglines", default=False,
  220.                 help="Don't cut lines, but wrap them and still show them neatly")
  221.     parser.add_option("--prompt", action="store", type="string", dest="prompt", default="KingBash",
  222.                 help="Show a prompt.")
  223.     parser.add_option("--atime", action="store_true", dest="atime", default=False,
  224.                 help="sort by access time")
  225.     parser.add_option("--mtime", action="store_true", dest="mtime", default=False,
  226.                 help="sort by modification time")
  227.     parser.add_option("--reverse", action="store_true", dest="reverse", default=False,
  228.                 help="reverse all sorting")
  229.     global options
  230.     (options,args)=parser.parse_args()
  231.     del args
  232.  
  233.     options.bashcompletionexception=options.bashcompletionexception.split()
  234.  
  235.     try:
  236.         screen=int(terminal_size()[0]/3)
  237.         width=terminal_size()[1]
  238.         if screen == 0: screen=0/0
  239.     except:
  240.         screen=23
  241.         width=80
  242.     try:
  243.         forceheight=int(options.forceheight)
  244.     except:
  245.         if options.forceheight=="MAX":
  246.             try:
  247.                 forceheight=terminal_size()[0]
  248.             except: pass
  249.     if forceheight != 0: screen=forceheight -3
  250.  
  251.     original_screen=screen
  252.  
  253.     if dmenu_mode or options.readfile:
  254.         if dmenu_mode:
  255.             f=os.fdopen(sys.stdin.fileno(), errors="ignore")
  256.         else:
  257.             f=open(options.readfile, errors="ignore")
  258.         file_mode=True
  259.         gc.disable()
  260.         read_file=[]
  261.         for read_f in f:
  262.             read_file.append(read_f[:-1])
  263.         if options.uniquereversesort: #still slower than bash's tac + cat + sort *2 + cut
  264.             seen={}
  265.             new_read_file=[]
  266.             for read_f in reversed(read_file):
  267.                 if read_f in seen: continue
  268.                 seen[read_f]=1
  269.                 new_read_file.append(read_f)
  270.             read_file=new_read_file
  271.         f.close()
  272.         gc.enable()
  273.         (left_off,check_left_off,suggestions)=grep_list(line.split(), read_file, original_screen, [], 0, [], [])
  274. #       filedict=[]
  275. #       for suggestion in suggestions:
  276. #           filedict.append( { "isdir":False, "name":suggestion, "format":"\x1b[m"+suggestion, "rec":False })
  277.         filedict=suggestions
  278.         prework=""
  279.         workpath=""
  280.         is_command=True
  281.         upto=""
  282.         workstr=""
  283.         postwork=""
  284.         cursor=point
  285.     else:
  286.         file_mode=False
  287.         upto=line[:point]
  288.         first_space=re.search("[^\\\\] ", upto)
  289.         if first_space:
  290.             first_space=first_space.start()
  291.         else:
  292.             first_space=point
  293.         workstr=re.match("(.*(?<!\\\\)[ &;|=])(.*)", upto)
  294.         if not workstr:
  295.             prework=""
  296.             workstr=upto
  297.         else:
  298.             prework=workstr.group(1)
  299.             workstr=workstr.group(2)
  300.         #This separates further on $dollar and puts it INSIDE workstr
  301.         dollars=re.match("(.*(?<!\\\\))(\$.*)", workstr)
  302.         if dollars:
  303.             prework+=dollars.group(1)
  304.             workstr=dollars.group(2)
  305.  
  306.         postwork=line[point:]
  307.         workpath=re.match("(.*/)(.*)",workstr)
  308.         if not workpath:
  309.             workpath=""
  310.         else:
  311.             workstr=workpath.group(2)
  312.             workpath=workpath.group(1)
  313.         if len(workstr) != 0  and workstr[0] == "'":
  314.             workstr=unfixshell_singlequote(workstr)
  315.         else:
  316.             workstr=unfixshell(workstr)
  317.         filedict=[]
  318.    
  319.         is_command=False
  320.         if len(workpath) == 0:
  321.             if len(prework) == 0: is_command=True
  322.             else:
  323.                 if point <= first_space: is_command=True
  324.                 if prework[-5:] in ("sudo ", "type "): is_command=True
  325.                 if prework[-6:] in ("which "): is_command=True
  326.                 if prework[-7:] in ("whatis "): is_command=True
  327.                 if prework[-7:] in ("setsid "): is_command=True
  328.                 if prework[-8:] in ("whereis "): is_command=True
  329.                 if prework[-2:] in ("{ "): is_command=True
  330.                 if prework[-1]  in ("|", ";", "&", "(") : is_command=True
  331.                 if prework[-2:] in ("| ","; ","& ", "( "): is_command=True
  332.                 #However
  333.                 if prework[-1]  == "=" : is_command=False
  334.                 if prework[-2:] == "= ": is_comamnd=False
  335.         if workpath != "" and not os.path.isdir(workpath):
  336.             workpath=re.sub("(^|(?<=/))(?P<a>[^~*\/])((?=/)|$)", "\g<a>*", workpath) #Presume single character paths like /u/b are a shortcut for /u*/b
  337.         if is_command:
  338.             suggestions=complete_command(workstr)
  339.         else:
  340.             if workpath == "" and re.search("\\bcd ", prework)!=None:
  341.                 try:
  342.                     cdpath=os.environ["CDPATH"].split(":")
  343.                     suggestions=[]
  344.                     for path in cdpath:
  345.                         try:
  346.                             if path[-1] != "/":
  347.                                 path+="/"
  348.                         except: #empty entry == .?
  349.                             pass
  350.                         suggestions.extend(map(lambda x: path+x,complete_filename(workstr, unfixshell(path))))
  351.                 except:
  352.                     suggestions=complete_filename(workstr, unfixshell(workpath))
  353.             elif re.search("(^|(?<!\\\\))\*", workpath) != None:
  354.                 suggestions=[]
  355.                 ast_paths=Popen("bash -c 'shopt -s nocaseglob; ls -d -1 "+workpath+"/'", shell=True, stdout=PIPE).communicate()[0].decode("utf8").split("\n") #Cheating
  356. #               sys.stderr.write(repr(ast_paths))
  357. #               sys.stderr.flush()
  358. #               os.system("read -p ENTER")
  359.                 for path in ast_paths:
  360.                     if path == "": continue
  361.                     try:
  362.                         if path[-1] != "/":
  363.                             path+="/"
  364.                     except: pass
  365.                     suggestions.extend(map(lambda x: path+x, complete_filename(workstr, unfixshell(path))))
  366.                 workpath=""
  367.             else:
  368.                 suggestions=complete_filename(workstr, unfixshell(workpath))
  369.         suggestions=sorted(set(suggestions),key=str.lower)
  370.         if options.bashcompletion and os.path.isfile("/etc/bash_completion"):
  371.             lastcommand_rx=re.sub("(.*(?<!\\\\)[&;|])(.*)", "", upto)
  372.             lastcommand=lastcommand_rx.split()
  373.             if len(lastcommand_rx) > 0 and lastcommand_rx[-1]==" ": lastcommand.append("")
  374.             if len(lastcommand) > 0 and lastcommand[0] not in options.bashcompletionexception and os.path.isfile("/etc/bash_completion.d/"+lastcommand[0]):
  375.                 lc_l=""
  376.                 for lc_n,lc_c in enumerate(lastcommand):
  377.                     lc_l+='; COMP_WORDS['+str(lc_n)+']="'+lc_c+'"'
  378.                 cur=""
  379.                 prev=""
  380.                 if len(lastcommand) > 0: cur=lastcommand[-1]
  381.                 if len(lastcommand) > 1: prev=lastcommand[-2]
  382. #               bc_sug=Popen("bash -c 'function have() { :; }; source <(cat /etc/bash_completion.d/"+lastcommand[0]+")"+lc_l+"; COMP_CWORD=$(( ${#COMP_WORDS[@]} -1)); _"+lastcommand[0]+"; echo ${COMPREPLY[@]}'", shell=True, stdout=PIPE).communicate()[0].split()
  383. #               bc_sug_utf=[]
  384. #               bc_sug=Popen("bash -c 'source /etc/bash_completion; function have() { :; }; source /etc/bash_completion.d/"+lastcommand[0]+lc_l+"; COMP_WORDBREAKS=' '; COMP_CWORD=$(( ${#COMP_WORDS[@]} -1)); COMP_LINE=lastcommand_rx; COMP_POINT="+str(len(lastcommand_rx))+"; _"+lastcommand[0]+"""; for opt in "${COMPREPLY[@]}"; do echo "$opt"; done '""", shell=True, stdout=PIPE).communicate()[0].decode("utf8").split("\n")
  385.                 bc_sug=Popen("""bash -c '
  386.     source /etc/bash_completion
  387.     function have() { :; }
  388.     source /etc/bash_completion.d/"""+lastcommand[0]+lc_l+"""
  389.     COMP_CWORD=$(( ${#COMP_WORDS[@]} -1))
  390.     function _get_comp_words_by_ref() { cur="""+fixshell(cur)+"""; prev="""+fixshell(prev)+"""; }
  391.     _"""+lastcommand[0]+"""
  392.     for opt in "${COMPREPLY[@]}"; do echo "$opt"; done '""", shell=True, stdout=PIPE).communicate()[0].decode("utf8").split("\n")
  393.                 for bc_nr in reversed(range(len(bc_sug))):
  394.                     bc_sug[bc_nr]=bc_sug[bc_nr].strip()
  395.                     if bc_sug[bc_nr] == "": del bc_sug[bc_nr]
  396.                 bc_sug.extend(suggestions)
  397.                 suggestions=bc_sug
  398.         if len(suggestions) == 1 and not (len(workstr) == 0 and options.rerun):
  399.             fix=fixshell(suggestions[0])
  400.             if not os.path.isdir(os.path.expandvars(os.path.expanduser(unfixshell(workpath))) + suggestions[0]):
  401.                 fix+=" "
  402.             else:
  403.                 fix+="/"
  404.                 if options.rerun:
  405.                     rerun_fn(prework+workpath+fix+postwork, str(len(prework+workpath+fix)))
  406.             print(prework + workpath + fix + postwork)
  407.             print(len((prework + workpath + fix).encode("utf8")))
  408.             return
  409.         if len(suggestions) == 0:
  410.             if len(workstr) != 0:
  411.                 if options.rerun:
  412.                     print(line)
  413.                     print(original_point)
  414.                     return
  415.                 else:
  416.                     lastword=re.match("(.* )(.*)", line)
  417.                     if not lastword:
  418.                         print(line)
  419.                         print(original_point)
  420.                         return
  421.                     else:
  422.                         firstwords=lastword.group(1)
  423.                         lastword=lastword.group(2)
  424.                     totalrestart=firstwords+lastword
  425.                     total_re=re.match("(.*[/ ])(.*)", totalrestart)
  426.                     if not total_re:
  427.                         total_final=firstwords+lastword
  428.                     else:
  429.                         total_final=total_re.group(1)+"*"+total_re.group(2)
  430. #                   os.environ["READLINE_LINE"]=firstwords+"*"+lastword
  431.                     os.environ["READLINE_LINE"]=total_final
  432.                     os.environ["READLINE_POINT"]=str(original_point+1)
  433.     #               curops=[sys.argv[0], "--rerun", "--selected", str(insert_cursor_move[0]), "--viewmin", str(insert_cursor_move[1])]
  434.                     curops=[sys.argv[0], "--rerun"]
  435.                     if options.extracommands != "": curops.extend(["--extracommands",options.extracommands])
  436.                     if options.extrafiles != "": curops.extend(["--extrafiles",options.extrafiles])
  437.                     if options.forceheight != 0: curops.extend(["--forceheight", options.forceheight])
  438.                     if options.noretab_az: curops.append("--noretab-az")
  439.                     if options.noretab_backspace: curops.append("--noretab-backspace")
  440.                     if options.noretab_directory: curops.append("--noretab-directory")
  441.                     if options.plustoall: curops.append("--plustoall")
  442.                     if options.bashcompletion: curops.append("--bashcompletion")
  443.                     if options.reverse: curops.append("--reverse")
  444.                     if options.atime: curops.append("--atime")
  445.                     if options.mtime: curops.append("--mtime")
  446.                     curops.append("--prompt")
  447.                     curops.append(options.prompt)
  448.                     curops.append("--bashcompletionexception")
  449.                     curops.append(" ".join(options.bashcompletionexception))
  450.                     if options.wraplonglines: curops.append("--wraplonglines")
  451.                     os.execv(sys.argv[0], curops)
  452.                     return
  453.             else:
  454.                 suggestions=[""]
  455.         if len(workstr) > 0 and workstr[-1] == '\\': workstr=workstr[:-1] #Don't escape the escape for a future character
  456.         first=suggestions[0]
  457.         lcd=[] #find largest common divider
  458.         for last in suggestions[1:]:
  459.             ld=""
  460.             for i in range(min(len(first),len(last))):
  461.                 if first[i].upper()==last[i].upper():
  462.                     ld=ld+first[i]
  463.                 else:
  464.                     break
  465.             lcd.append(ld)
  466.         if len(lcd) > 0:
  467.             smallest=sorted(lcd)[0]
  468.         else:
  469.             smallest=""
  470.         if len(smallest) > len(workstr):
  471.             workstr=smallest
  472.         line=prework+workpath+fixshell(workstr)+postwork
  473.         point=len(prework+workpath+fixshell(workstr))
  474.         cursor=len(workpath+workstr)
  475.         if is_command:
  476.             for suggestion in suggestions:
  477.                 filedict.append({ "isdir":False, "name":suggestion, "format":"\x1b[7m" + suggestion[:cursor] + "\x1b[m" + suggestion[cursor:], "rec":False, "time":0 })
  478.         else:
  479.             for suggestion in suggestions:
  480.                 try:
  481.                     statf=os.lstat(os.path.expandvars(os.path.expanduser(unfixshell(workpath)))+suggestion)
  482.                 except OSError:
  483.                     filedict.append({"isdir":False, "name":suggestion, "format":"\x1b[m[SPC]".ljust(12)+suggestion, "rec":True, "time":0})
  484.                     continue
  485.                 link_ind=""
  486.                 if stat.S_ISLNK(statf[stat.ST_MODE]):
  487.                     link_ind=" -> "+os.readlink(os.path.expandvars(os.path.expanduser(unfixshell(workpath)))+suggestion)
  488.                     try:
  489.                         statf=os.stat(os.path.expandvars(os.path.expanduser(unfixshell(workpath)))+suggestion)
  490.                         link_ind=" @"+link_ind
  491.                     except OSError:
  492.                         link_ind=" @BAD"+link_ind
  493.                 time_string=""
  494.                 time_int=0
  495.                 if options.atime or options.mtime:
  496.                     if options.atime:
  497.                         time_int=statf[stat.ST_ATIME]
  498.                     if options.mtime:
  499.                         time_int=statf[stat.ST_MTIME]
  500.                     time_string=time.localtime(time_int)
  501.                     time_string="["+str(time_string[0])+"-"+str(time_string[1]).rjust(2,"0")+"-"+str(time_string[2]).rjust(2,"0")+" "+str(time_string[3]).rjust(2,"0")+":"+str(time_string[4]).rjust(2,"0")+"] "
  502.                 size=statf[stat.ST_SIZE]
  503.                 exp=0
  504.                 while size >= 1024: size=int(size/1024); exp=exp+1
  505.                 size=str(size)+["B","KB","MB","GB","TB"][exp]
  506.                 if stat.S_ISDIR(statf[stat.ST_MODE]):
  507.                     dirappend="/"
  508.                 else:
  509.                     dirappend=""
  510.                 try:
  511.                     if isrecommended(re.sub(".*[^\\\\][;&|]", "", prework).split()[0], suggestion, dirappend=="/"):
  512.                         rec=True
  513.                         color="\x1b[m\x1b[31m"
  514.                         matchcolor="\x1b[31;47m"
  515.                     else:
  516.                         rec=False
  517.                         color="\x1b[m"
  518.                         matchcolor="\x1b[30;47m"
  519.                 except IndexError:
  520.                     rec=False
  521.                     color="\x1b[m"
  522.                     matchcolor="\x1b[30;47m"
  523.                 filedict.append({ "isdir":dirappend=="/", "name":suggestion, "format":time_string+"\x1b[m"+("\x1b[35m(" + size + ")\x1b[m").ljust(17) + matchcolor + str(workpath+suggestion)[:cursor] + color + str(workpath+suggestion)[cursor:] + dirappend + link_ind, "rec":rec, "time":time_int})
  524.         filedict.sort(key=operator.itemgetter("isdir"),reverse=True)
  525.         if options.atime or options.mtime:
  526.             filedict.sort(key=operator.itemgetter("time"))
  527.         if options.reverse:
  528.             filedict.reverse()
  529.         filedict.sort(key=operator.itemgetter("rec"),reverse=True)
  530.  
  531.     #They default to 0 in optparse
  532.     selected=options.selected
  533.     viewmin=options.viewmin
  534.  
  535.     count=len(filedict)-1
  536.     if count < screen: screen=count
  537.  
  538.  
  539.     #Turn off echo mode (print what you type)
  540.     try:
  541.         fd=sys.stdin.fileno()
  542.         oldterm=termios.tcgetattr(fd)
  543.     except termios.error:
  544.         newin=os.open("/dev/tty", os.O_RDONLY)
  545.         os.dup2(newin, fd)
  546.         oldterm=termios.tcgetattr(fd)
  547.     newattr=termios.tcgetattr(fd)
  548.     newattr[3]=newattr[3] & ~termios.ICANON & ~termios.ECHO
  549.     termios.tcsetattr(fd, termios.TCSANOW, newattr)
  550.  
  551.     #This makes the cursor invisible, and clears the possible calling-function's replacement prompt
  552.     sys.stderr.write("\x1b[?25l\r\x1b[2K")
  553.    
  554.     file_mode_changes=False
  555.     change_was_scroll=False
  556.     show_F1_help_text=False
  557.     new_list_is_subset=False
  558.     calculate_all=False
  559.     rerun=False
  560.     insert_cursor_move=False
  561.     already_erased=False
  562.     not_done_recheck=[]
  563.     recheck=[]
  564.     viewmax=False
  565.     oldselected=0
  566.  
  567.     try:
  568.         while 1:
  569.             if show_F1_help_text:
  570.                 F1_help_text()
  571.                 show_F1_help_text=False
  572.             if file_mode and file_mode_changes:
  573.                 recheck=[]
  574.                 if new_list_is_subset:
  575.                     original_read_file=read_file
  576.                     recheck=suggestions
  577.                     suggestions=[]
  578.                     viewmin=0
  579.                     selected=0
  580. #                   check_left_off=0
  581.                     new_list_is_subset=False
  582.                 elif not change_was_scroll:
  583.                     suggestions=[]
  584.                     check_left_off=0
  585.                     left_off=0
  586. #                   check_left_off=0
  587.                     recheck=[]
  588.                     not_done_recheck=[]
  589.                     viewmin=0
  590.                     selected=0
  591.                     change_was_scroll=False
  592.                 if calculate_all:
  593.                     backup_original_screen=original_screen
  594.                     original_screen=len(read_file)
  595.                 (left_off,not_done_recheck,suggestions)=grep_list(line.split(), read_file, original_screen, suggestions, left_off, recheck, not_done_recheck)
  596.                 if calculate_all:
  597.                     selected=len(suggestions)-1
  598.                     original_screen=backup_original_screen
  599.                     while viewmin+screen < selected: viewmin+=screen+1
  600.                     calculate_all=False
  601.                 filedict=[]
  602. #               gc.disable()
  603. #               for suggestion in suggestions:
  604. #                   filedict.append( { "isdir":False, "name":suggestion, "format":"\x1b[m"+suggestion, "rec":False })
  605. #               gc.enable()
  606.                 filedict=suggestions
  607.                 oldcount=count
  608.                 count=len(filedict)-1
  609.  
  610.                 if count < 0: count = 0
  611.                 if count < original_screen:
  612.                     screen=count
  613.                 if screen != original_screen and count > original_screen:
  614.                     screen=original_screen
  615. #               if not change_was_scroll and oldcount != count: selected=0
  616.                 change_was_scroll=False
  617.                 file_mode_changes=False
  618.             try:
  619.                 unused = line[point+1:]
  620.                 sys.stderr.write(options.prompt+"> " + line[:point] + '\x1b[7m' + line[point] + '\x1b[m' + line[point+1:] + '\n')
  621.             except IndexError:
  622.                 sys.stderr.write(options.prompt+"> " + line + '\x1b[7m \x1b[m\n')
  623.             oldline=line
  624.             (wrapline_blockscroll, wrapline_blockscroll_bottom, wrapline_todelete)=show_filelist(filedict, selected, viewmin, viewmax, screen, width, original_screen, int(col_count(options.prompt+">  "+oldline)/width))
  625. #           sys.stderr.write( repr( (wrapline_blockscroll, wrapline_blockscroll_bottom, wrapline_todelete, viewmin, viewmax, selected, screen, original_screen) ) )
  626.  
  627.             sys.stderr.write("-"*width)
  628.             sys.stderr.flush()
  629.             try:
  630.                 c=sys.stdin.read(1)
  631.                 if c == "\x1b":
  632.                     c="ESC"
  633.                     oldflags=fcntl.fcntl(fd, fcntl.F_GETFL)
  634.                     fcntl.fcntl(fd, fcntl.F_SETFL, oldflags | os.O_NONBLOCK)
  635.                     while 1:
  636.                         try:
  637.                             newc=sys.stdin.read(1)
  638.                             if newc == "": break # Python 3 doesn't seem to use IOError
  639.                             if newc == "\x1b": newc="ESC"
  640.                             c=c+newc
  641.                             if newc in ("A","B","C","D","a","b","c","d","~"): #Common endings of escape codes of keys that are held down
  642.                                 break
  643.                         except IOError: break
  644.                     fcntl.fcntl(fd, fcntl.F_SETFL, oldflags)
  645.                 if   c == "ESC[A":#UP
  646.                     selected-=1
  647.                 elif c in ("ESC[a","ESC[1;2A"):#SHIFT UP
  648.                     selected-=5
  649.                 elif c == "ESC[B":#DOWN
  650.                     selected+=1
  651.                 elif c in ("ESC[b","ESC[1;2B"):#SHIFT DOWN
  652.                     selected+=5
  653.                 elif c == "ESC[C":#RIGHT
  654.                     if file_mode:
  655.                         if point != len(line):
  656.                             point+=1
  657.                     else:
  658.                         filedict[selected]["name"]=fixshell(filedict[selected]["name"])
  659.                         if not filedict[selected]["isdir"]:
  660.                             filedict[selected]["name"]+=" "
  661.                         else:
  662.                             filedict[selected]["name"]+="/"
  663.                         line=prework + workpath + filedict[selected]["name"] + postwork
  664.                         point=len(prework + workpath + filedict[selected]["name"])
  665.                         if not options.noretab_directory:
  666.                             rerun=True
  667.                         break
  668.                 elif c in ("ESC[D","\x0c"):#LEFT
  669.                     if file_mode:
  670.                         if point != 0:
  671.                             point-=1
  672.                     else:
  673.                         workstr=""
  674.                         oldlen=len(workpath)
  675.                         if workpath == "/":
  676.                             pass
  677.                         elif workpath == "" or workpath[-3:] == "../":
  678.                             workpath+="../"
  679.                         else:
  680.                             oldpath=workpath
  681.                             workpath=re.sub("/[^/]*/$", "/", workpath)
  682.                             if oldpath == workpath:
  683.                                 workpath=re.sub(".*?/$", "", workpath)
  684.                             del oldpath
  685.                         line=prework+workpath+fixshell(workstr)+postwork
  686.                         point=point+(len(workpath)-oldlen)
  687.                         if not options.noretab_directory:
  688.                             rerun=True
  689.                         break
  690.                 elif c == "ESC[6~":#PAGEDOWN
  691.                     selected+=screen+1
  692. #                   if viewmin + screen + 1 < count: viewmin+=screen+1
  693.                 elif c == "ESC[5~":#PAGEUP
  694.                     selected-=screen+1
  695. #                   viewmin-=screen+1
  696.                 elif c in ("ESC[4~","ESC[F","ESC[8~","ESCOF"):#END
  697.                     if selected != count: selected=count+1 #trick to activate below viewmin code
  698.                     if file_mode and left_off != len(read_file) or len(recheck)!=0:
  699.                         calculate_all=True
  700.                         file_mode_changes=True
  701.                 elif c in ("ESC[1~","ESC[H","ESC[7~","ESCOH"):#HOME
  702.                     selected=0
  703.                     viewmin=0
  704.                 elif c in ("\x0d", "\n"):#ENTER
  705.                     if not file_mode:
  706.                         file=fixshell(filedict[selected]["name"])
  707.                         if not filedict[selected]["isdir"]:
  708.                             file+=" "
  709.                         else:
  710.                             file+="/"
  711.                     else:
  712.                         try:
  713.                             file=filedict[selected]
  714.                         except IndexError:
  715.                             file=line
  716.                             pass
  717.                     line=prework + workpath + file + postwork
  718.                     point=len((prework + workpath + file).encode("utf8"))
  719.                     break
  720.                 elif c == "ESC":#ESCAPE
  721.                     if file_mode:
  722.                         line=original_line
  723.                         point=original_point
  724.                     break
  725.                 elif c in ("\x7f", "\x08"):#BACKSPACE
  726.                     if file_mode:
  727.                         if point != 0:
  728.                             line=line[:point-1]+line[point:]
  729.                             point-=1
  730.                             file_mode_changes=True
  731.                     else:
  732.                         line=prework+str(workpath+workstr)[:-1]+postwork
  733.                         point-=1
  734.                         if not options.noretab_backspace:
  735.                             rerun=True
  736.                         break
  737.                 elif c in ("ESC\x7f", "ESC\x08"):#ALT+BACKSPACE
  738.                     if file_mode:
  739.                         try:
  740.                             nline=" ".join(line[:point].split(" ")[:-1])+line[point:]
  741.                             point-=len(line)-len(nline)
  742.                             line=nline
  743.                             del nline
  744.                             file_mode_changes=True
  745.                         except: pass
  746.                     else:
  747.                         new=re.sub("""[/ '"]?[^/ '"]*?[/ '"]?$""", "", prework+workpath+fixshell(workstr))
  748.                         line=new+postwork
  749.                         point=len(new)
  750.                         if not options.noretab_backspace:
  751.                             rerun=True
  752.                         break
  753.                 elif c in ("ESC[d","ESC[1;2D","ESCESC[D"):#SHIFT LEFT AND ALT LEFT
  754.                     newpoint=len(" ".join(line[:point].split(" ")[:-1]))
  755.                     if point != newpoint:
  756.                         point=newpoint
  757.                         if not file_mode:
  758.                             break
  759.                 elif c in ("ESC[c","ESC[1;2C","ESCESC[C"):#SHIFT RIGHT AND ALT RIGHT
  760.                     newpoint=len(line[:point+1]+line[point+1:].split(" ")[0])
  761.                     if point != newpoint:
  762.                         point=newpoint
  763.                         if not file_mode:
  764.                             break
  765.                 elif c == "\x01":#^A
  766.                     if point != 0:
  767.                         point=0
  768.                         if not file_mode:
  769.                             break
  770.                 elif c == "\x05":#^E
  771.                     if point != len(line):
  772.                         point=len(line)
  773.                         if not file_mode:
  774.                             break
  775.                 elif c == "ESC[3~":#DELETE
  776.                     if file_mode:
  777.                         line=line[:point]+line[point+1:]
  778.                         file_mode_changes=True
  779.                 elif c == "\x15":#CONTROL + U
  780.                     line=""
  781.                     point=0
  782.                     if not file_mode:
  783.                         break
  784.                     file_mode_changes=True
  785.                 elif c in ("\x09",'\t'):#TAB
  786.                     if file_mode:
  787.                         if options.historytabcompletion:
  788.                             os.environ["READLINE_LINE"]=line
  789.                             os.environ["READLINE_POINT"]=str(point)
  790. #                           erase_filelist(screen+2 + int( col_count("KingBash>  "+line)/width) + wrapline_todelete)
  791.                             erase_filelist(1 + int( col_count(options.prompt+">  "+oldline)/width) + wrapline_todelete)
  792.                             already_erased=True
  793.                             output=Popen([sys.argv[0], "--plustoall", "--noretab-backspace"], stdout=PIPE).communicate()[0].decode("utf8").split("\n")
  794.                             newline="\n".join(output[0:-2])
  795.                             if newline != line:
  796.                                 file_mode_changes=True
  797.                             line=newline
  798.                             point=int(output[-2])
  799.                         else:
  800.                             try:
  801.                                 line=filedict[selected]
  802.                                 point=len(filedict[selected])
  803.                             except IndexError:
  804.                                 pass
  805.                     else:
  806.                         line=prework + workpath
  807.                         point=len(prework + workpath)
  808.                         break
  809.                 elif c == "ESC[2~":#INSERT
  810.                     if not file_mode:
  811.                         file=fixshell(filedict[selected]["name"])
  812.                         if not filedict[selected]["isdir"]:
  813.                             file+=" "
  814.                         else:
  815.                             file+="/ "
  816.                     else:
  817.                         try:
  818.                             file=filedict[selected]
  819.                         except IndexError:
  820.                             file=""
  821.                     line=prework + workpath + file + workpath + fixshell(workstr) + postwork
  822.                     point=len((prework + workpath + file + workpath + fixshell(workstr)).encode("utf8"))
  823.                     if not file_mode:
  824.                         rerun=True
  825.                         selected+=1
  826.                         if selected > count: selected=count
  827.                         if selected > viewmin + screen: viewmin+=screen+1
  828.                         insert_cursor_move=(str(selected), str(viewmin))
  829.                         break
  830.                     else:
  831.                         file_mode_changes=True
  832.                 elif ((c == "*" and not options.plustoall) or (c == "+" and options.plustoall)) and not file_mode:#ASTERISK
  833.                     line=prework
  834.                     point=len(prework.encode("utf8"))
  835.                     allfalse=True
  836.                     for file in filedict:
  837.                         if file["rec"] == False: continue
  838.                         allfalse=False
  839.                         file["name"]=fixshell(file["name"])+" "
  840.                         line+=workpath+file["name"]
  841.                         point+=len( (workpath+file["name"]).encode("utf8"))
  842.                     if allfalse:
  843.                         for file in filedict:
  844.                             file["name"]=fixshell(file["name"])+" "
  845.                             line+=workpath + file["name"]
  846.                             point+=len( (workpath + file["name"]).encode("utf8"))
  847.                     line+=postwork
  848.                     break
  849.                 elif c in ("ESC[13~","ESCOR"):#F3
  850.                     if not options.atime and not options.mtime:
  851.                         options.atime=True
  852.                         options.reverse=True
  853.                         options.prompt="ATIME sort"
  854.                     elif not options.mtime:
  855.                         options.mtime=True
  856.                         options.atime=False
  857.                         options.reverse=True
  858.                         options.prompt="MTIME sort"
  859.                     else:
  860.                         options.atime=False
  861.                         options.mtime=False
  862.                         options.reverse=False
  863.                         options.prompt="ALPHA sort"
  864.                     rerun=True
  865.                     break
  866.                 elif c in ("ESC[12~","ESCOQ"):#F2
  867.                     if not is_command:
  868.                         prework=re.sub("^.*?(?<!\\\\) ", "", prework) #remove first word and move cursor there
  869.                         prework=" "+prework
  870.                         filedict[selected]["name"]=fixshell(filedict[selected]["name"])
  871.                         if not filedict[selected]["isdir"]:
  872.                             filedict[selected]["name"]+=" "
  873.                         else:
  874.                             filedict[selected]["name"]+="/"
  875.                         line=prework + workpath + filedict[selected]["name"] + postwork
  876.                         point=0
  877.                         break
  878.                     else:
  879.                         break
  880.                 elif c in ("ESC[11~","ESCOP"):#F1
  881.                     show_F1_help_text=True
  882.                 else:
  883.                     if file_mode:
  884.                         if line[:point].split(" ")[-1]+line[point:].split(" ")[0] in line[:point].split(" ")[-1]+ c +line[point:].split(" ")[0]:
  885.                             new_list_is_subset=True
  886.                         line=line[:point] + c + line[point:]
  887.                         point+=len(c)
  888.                         file_mode_changes=True
  889.                     else:
  890.                         point=len(prework + workpath + fixshell(workstr) + c)
  891.                         line=prework + workpath + fixshell(workstr) + c + postwork
  892.                         if not options.noretab_az and c not in (' ',"="):
  893.                             rerun=True
  894.                         break
  895.             except IOError: pass
  896.             if selected <= 0:
  897.                 selected=0
  898.                 viewmax=False
  899.             elif selected >= count:
  900.                 selected=count
  901.                 while selected > viewmin + screen: viewmin+=screen+1
  902.                 viewmax=False
  903.             elif selected > viewmin + screen - wrapline_blockscroll or (viewmax and selected > viewmax):
  904.                 viewmin+=screen+1-wrapline_blockscroll
  905.                 viewmax=False
  906.             elif selected < viewmin + wrapline_blockscroll_bottom or (viewmax and selected < viewmax-(wrapline_todelete - wrapline_blockscroll_bottom)):
  907.                 if selected == oldselected-1:
  908.                     viewmax=selected
  909.                 else:
  910.                     frombottom=oldselected-viewmin
  911.                     fromtop=(viewmin+screen+1) - oldselected
  912.                     if frombottom < fromtop:
  913.                         viewmax=False
  914.                     else:
  915.                         viewmax=selected+fromtop
  916.                 viewmin-=screen+1+wrapline_blockscroll_bottom
  917.             if viewmin < 0: viewmin=0
  918.             if selected < viewmin:
  919.                     viewmin=selected
  920.             if file_mode and not options.slowhistory and left_off != len(read_file) and viewmin+screen+2 > len(suggestions):
  921.                 file_mode_changes=True
  922.                 change_was_scroll=True
  923.             if not already_erased:
  924.                 erase_filelist(1 + int(col_count(options.prompt+">  "+oldline)/width) + wrapline_todelete)
  925.             already_erased=False
  926.             oldselected=selected
  927.     except KeyboardInterrupt:
  928.         if file_mode:
  929.             line=original_line
  930.             point=original_point
  931.     finally:
  932.         sys.stderr.write('\x1b[m\x1b[?25h') #default color, make cursor visible
  933.         erase_filelist(1 + int(col_count(options.prompt+">  "+oldline)/width) + wrapline_todelete)
  934.         termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm) #restore echo mode
  935.     if rerun:
  936.         rerun_fn(line, point, insert_cursor_move)
  937.     else:
  938.         print(line)
  939.         if not dmenu_mode:
  940.             print(point)
  941.  
  942. def rerun_fn(line,point,insert_cursor_move=None):
  943.     os.environ["READLINE_LINE"]=line
  944.     os.environ["READLINE_POINT"]=str(point)
  945.     if not insert_cursor_move:
  946.         del insert_cursor_move
  947.         insert_cursor_move=[0,0]
  948.     curops=[sys.argv[0], "--rerun", "--selected", str(insert_cursor_move[0]), "--viewmin", str(insert_cursor_move[1])]
  949.     if options.extracommands != "": curops.extend(["--extracommands",options.extracommands])
  950.     if options.extrafiles != "": curops.extend(["--extrafiles",options.extrafiles])
  951.     if options.forceheight != 0: curops.extend(["--forceheight", options.forceheight])
  952.     if options.noretab_az: curops.append("--noretab-az")
  953.     if options.noretab_backspace: curops.append("--noretab-backspace")
  954.     if options.noretab_directory: curops.append("--noretab-directory")
  955.     if options.plustoall: curops.append("--plustoall")
  956.     if options.bashcompletion: curops.append("--bashcompletion")
  957.     if options.reverse: curops.append("--reverse")
  958.     if options.atime: curops.append("--atime")
  959.     if options.mtime: curops.append("--mtime")
  960.     curops.append("--prompt")
  961.     curops.append(options.prompt)
  962.     curops.append("--bashcompletionexception")
  963.     curops.append(" ".join(options.bashcompletionexception))
  964.     if options.wraplonglines: curops.append("--wraplonglines")
  965.     os.execv(sys.argv[0], curops)
  966.  
  967. # Thanks to http://bytes.com/topic/python/answers/453313-best-way-finding-terminal-width-height
  968. def ioctl_GWINSZ(fd): #### TABULATION FUNCTIONS
  969.     try: ### Discover terminal width
  970.         cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'))
  971.     except:
  972.         return None
  973.     return cr
  974. def terminal_size():
  975. ### decide on *some* terminal size
  976.     # try open fds
  977.     cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
  978.     if not cr:
  979.         # ...then ctty
  980.         try:
  981.             fd = os.open(os.ctermid(), os.O_RDONLY)
  982.             cr = ioctl_GWINSZ(fd)
  983.             os.close(fd)
  984.         except:
  985.             pass
  986.     return int(cr[0]), int(cr[1])
  987.  
  988. def complete_command(workstr):
  989.     paths=os.environ["PATH"].split(":")
  990.     paths.append("COMPLETE_ALIASES")
  991.     suggestions=[]
  992.     for path in paths:
  993.         suggestions.extend(complete_filename(workstr, path))
  994.     return suggestions
  995. def complete_filename(workstr, path):
  996.     if path == "": path="./"
  997.     path=os.path.expandvars(os.path.expanduser(path))
  998.     suggestions=[]
  999.     try:
  1000.         if path == "COMPLETE_ALIASES":
  1001.             if options.extracommands != "":
  1002.                 filelist=options.extracommands.split("\n")
  1003.             else:
  1004.                 return []
  1005.         else:
  1006.             filelist=os.listdir(path)
  1007.             if options.extrafiles != "" and len(workstr)>0 and workstr[0]=="$":
  1008.                 filelist.extend(options.extrafiles.split("\n"))
  1009.             if workstr=="..": filelist.append("..")
  1010.         for file in filelist:
  1011.             try:
  1012.                 asterisk=re.escape(workstr)
  1013.                 asterisk=re.sub("\\\\\*",".*",asterisk)
  1014.                 asterisk=re.sub("\\\\\?",".?",asterisk)
  1015.                 asterisk+=".*"
  1016.                 asterisk_match=re.match(asterisk,file,re.I)
  1017.                 if asterisk_match:
  1018.                     if len(file) > 0 and file[0] != "$":
  1019.                         file=re.sub("\\$","\\$",file)
  1020.                     suggestions.append(file)
  1021.                 else:
  1022.                     asterisk_match=re.match(asterisk,fixshell(file),re.I)
  1023.                     if asterisk_match:
  1024.                         if len(file) > 0 and file[0] != "$":
  1025.                             file=re.sub("\\$","\\$",file)
  1026.                         suggestions.append(file)
  1027.             except ValueError: pass
  1028.     except OSError: pass
  1029.     return suggestions
  1030. def grep_list(patterns, lines, count, good_list, left_off, recheck, not_done_recheck):
  1031.     gc.disable()
  1032.     count*=options.loadpages #pages loaded at once (for quick page down)
  1033.     recheck_left_off=0
  1034.     not_done_recheck_left_off=0
  1035.     for line in recheck:
  1036.         recheck_left_off+=1
  1037.         found=True
  1038.         for pattern in patterns:
  1039.             if pattern.lower() not in line.lower():
  1040.                 found=False
  1041.                 break
  1042.         if found:# and (len(good_list) == 0 or good_list[-1] != line):
  1043.             good_list.append(line)
  1044.             if options.slowhistory: continue
  1045.             count-=1
  1046.             if count < 0:
  1047.                 break
  1048.     recheck=recheck[recheck_left_off:]
  1049.     if count >= 0:
  1050.         for line in not_done_recheck:
  1051.             not_done_recheck_left_off+=1
  1052.             found=True
  1053.             for pattern in patterns:
  1054.                 if pattern.lower() not in line.lower():
  1055.                     found=False
  1056.                     break
  1057.             if found and (len(good_list) == 0 or good_list[-1] != line):
  1058.                 good_list.append(line)
  1059.                 if options.slowhistory: continue
  1060.                 count-=1
  1061.                 if count < 0:
  1062.                     break
  1063.         recheck=not_done_recheck[not_done_recheck_left_off:]
  1064.         if count >= 0:
  1065.             for line in lines[left_off:]:
  1066.                 left_off+=1
  1067.                 found=True
  1068.                 for pattern in patterns:
  1069.                     if pattern.lower() not in line.lower():
  1070.                         found=False
  1071.                         break
  1072.                 if found and (len(good_list) == 0 or good_list[-1] != line):
  1073.                     good_list.append(line)
  1074.                     if options.slowhistory: continue
  1075.                     count-=1
  1076.                     if count < 0:
  1077.                         break
  1078.     else:
  1079.         recheck.extend(not_done_recheck)
  1080.     gc.enable()
  1081.     return (left_off, recheck, good_list)
  1082.  
  1083. def show_filelist(filedict, selected, viewmin, viewmax, screen, width, original_screen, prompt_extra_lines):
  1084.     original_screen-=prompt_extra_lines
  1085.     length=len(filedict)-1
  1086.     eofs=viewmin+screen
  1087.     if eofs > length:
  1088.         modeofs=length
  1089.     else:
  1090.         modeofs=eofs
  1091.     try:
  1092.         if length % screen != 0:
  1093.             modlen=(screen-(length%screen)+length)
  1094.         else:
  1095.             modlen=length
  1096.     except ZeroDivisionError:
  1097.         modlen=1
  1098.     try:
  1099.         dotstart=int(screen*viewmin/modlen)+viewmin
  1100.         if dotstart == 2: dotstart=1
  1101.         dotend=int(screen*(eofs+1)/modlen)+viewmin-1
  1102.         if modeofs - viewmin == 0: modeofs=2
  1103.         selindicator=dotstart + int( (dotend - dotstart + 1) * (selected - viewmin) / (modeofs - viewmin +1) )
  1104.     except ZeroDivisionError:
  1105.         dotstart=1
  1106.         dotend=1
  1107.         selindicator=1
  1108.     if selindicator > eofs: selindicator=eofs
  1109.     if dotend < dotstart: dotend=dotstart
  1110.     wrapextralineuse=0
  1111.     #lineuse=0
  1112.     output=[]
  1113.     if not viewmax:
  1114.         viewrange=range(viewmin, viewmin+screen+1)
  1115.     else:
  1116.         below_max=viewmax-screen
  1117.         if below_max < 0: below_max=0
  1118.         viewrange=range(below_max, viewmax+1)
  1119.     for i in viewrange:
  1120.         #lineuse+=1
  1121.         outline="\x1b[m"
  1122.         if i<dotstart or i>dotend:
  1123.             scrollbar="  "
  1124.         elif i == selindicator:
  1125.             scrollbar="X "
  1126.         else:
  1127.             scrollbar="* "
  1128.         outline+=scrollbar
  1129.         try:
  1130.             try:
  1131.                 preprune=filedict[i]["format"]
  1132.             except TypeError:
  1133.                 preprune="\x1b[m"+filedict[i]
  1134.             list_of_prune=[]
  1135.             prunedline,prunedleft,leftover=cut_string(preprune, width-2)
  1136.             list_of_prune.append(outline+prunedline)
  1137.             if options.wraplonglines:
  1138.                 while len(leftover) > 0:
  1139.                     #if i+wrapextralineuse == viewmin+screen-1:
  1140.                     #   break
  1141.                     wrapextralineuse+=1
  1142.                     #prunedline+="\n"
  1143.                     newpl,prunedleft,leftover=cut_string(leftover, width-11)
  1144. #                   prunedline+=" "*(width-col_count(newpl)-2)+newpl
  1145.                     list_of_prune.append(scrollbar+" "*(width-col_count(newpl)-2)+newpl)
  1146.                     prunedleft=0
  1147.             if i == selected:
  1148.                 list_of_prune[-1]+="\x1b[36;46m"
  1149.                 while prunedleft > 0:
  1150.                     prunedleft-=1
  1151.                     list_of_prune[-1]+="#"
  1152.                 for counter in range(len(list_of_prune)):
  1153.                     list_of_prune[counter]=list_of_prune[counter].replace("\x1b[m","\x1b[m\x1b[30;46m")
  1154.             list_of_prune[-1]+="\x1b[m"
  1155.             output.append(list_of_prune)
  1156.         except IndexError:
  1157.             output.append([outline])
  1158.     i=0
  1159.     wrapextralineuse=0
  1160.     lineuse=0
  1161.     done=True
  1162.     if not viewmax:
  1163.         for counter in output:
  1164.             i+=len(counter)
  1165.             if i>original_screen+1:
  1166.                 wrapextralineuse+=1
  1167.                 i-=len(counter)
  1168.                 if len(counter) > 1: wrapextralineuse+=1
  1169.                 done=False
  1170.                 break
  1171.             wrapextralineuse+=len(counter)-1
  1172.             sys.stderr.write("\n".join(counter)+"\n")
  1173.         if done: wrapextralineuse=0
  1174.         return(wrapextralineuse, 0, i)
  1175.     else:
  1176.         for index in reversed(range(len(output))):
  1177.             i+=len(output[index])
  1178.             if i>original_screen+2:
  1179.                 i-=len(output[index])
  1180.                 index+=1
  1181.                 done=False
  1182.                 break
  1183.         for counter in output[index:]:
  1184.             wrapextralineuse+=len(counter)-1
  1185.             sys.stderr.write("\n".join(counter)+"\n")
  1186.         if done: wrapextralineuse=0
  1187.         return(0, wrapextralineuse, i)
  1188.        
  1189. def erase_filelist(screen):
  1190.     for unused in range(screen):
  1191.         sys.stderr.write("\x1b[2K\x1b[A")
  1192.     sys.stderr.write("\x1b[2K\r")
  1193.  
  1194. def unfixshell_singlequote(goodstr):
  1195.     badstr=re.sub("'\\\\''", "'", goodstr)
  1196.     if badstr[-1] == "'":
  1197.         return badstr[1:-1]
  1198.     else:
  1199.         return badstr[1:]
  1200. def unfixshell(goodstr):
  1201.     if len(goodstr) != 0 and goodstr[-1] == "\\":
  1202.         appendslash="\\"
  1203.     else:
  1204.         appendslash=""
  1205.     goodpieces=[]
  1206.     while "\\\\" in goodstr:
  1207.         index=goodstr.index("\\\\")
  1208.         goodpieces.append(goodstr[:index])
  1209.         try:
  1210.             goodstr=goodstr[index+2:]
  1211.         except IndexError: goodstr=""
  1212.     goodpieces.append(goodstr)
  1213.     goodstr=""
  1214.     for piece in goodpieces:
  1215.         goodstr=goodstr+re.sub("\\\\","",piece)+"\\"
  1216.     return goodstr[:-1]+appendslash
  1217. def fixshell(badstr):
  1218.     badchars=""" !@#^&*()`'"<>\\[]{};:=?|   """ #$ is done above only in specific cases
  1219.     goodstr=""
  1220.     prev=""
  1221.     for ch in badstr:
  1222.         if ch in badchars:
  1223.             ch="\\"+ch
  1224.         goodstr=goodstr+ch
  1225.     return re.sub("\\\\\\\\\$","\\$",goodstr) #Turn \\$ into \$
  1226.  
  1227. def cut_string(line, width):
  1228.     #Thanks to:
  1229.     #http://bugs.python.org/file14782/test_ucs2w.py
  1230.     newline=""
  1231.     wait_for_m=False
  1232.     leftover=""
  1233.     count=0
  1234.     gc.disable()
  1235.     for ch in line: #line.decode("utf8"): #filesystem encoding?
  1236.         if wait_for_m:
  1237.             newline+=ch
  1238.             if ch == 'm':
  1239.                 wait_for_m=False
  1240.             continue
  1241.         if ch == '\x1b':
  1242.             wait_for_m=True
  1243.             newline+=ch
  1244.             continue
  1245.         if ch == "\n":
  1246.             ch="?"
  1247.         if ch == "\t":
  1248.             if count +2 + 8 > width: break #+2 for scrollbar
  1249.             newline+=" "*(8-(count+2)%8)
  1250.             count+=8-(count+2)%8
  1251.             continue
  1252.         if east_asian_width(ch) in "WF":
  1253.             count+=1
  1254.         count+=1
  1255.         if not count > width:
  1256.             newline+=ch
  1257.         else:
  1258.             leftover+=ch
  1259.     gc.enable()
  1260.     #return newline.encode("utf8"),width-count
  1261.     return newline, width-count, leftover
  1262.  
  1263. def col_count(line):
  1264.     wait_for_m=False
  1265.     count=0
  1266.     gc.disable()
  1267.     for ch in line:
  1268.         if wait_for_m:
  1269.             if ch == 'm':
  1270.                 wait_for_m=False
  1271.             continue
  1272.         if ch == '\x1b':
  1273.             wait_for_m=True
  1274.             continue
  1275.         if ch == '\t':
  1276.             count+=8-(count+2)%8
  1277.             continue
  1278.         if east_asian_width(ch) in "WF":
  1279.             count+=2
  1280.             continue
  1281.         count+=1
  1282.     gc.enable()
  1283.     return count
  1284. def isrecommended(cmd, file, isdir):
  1285.     if cmd in ("cd","popd","rmdir"):
  1286.         return isdir
  1287. # http://wiki.archlinux.org/index.php/Common_Applications
  1288. # http://wiki.archlinux.org/index.php/Lightweight_Applications
  1289. # not sure if some of the binaries are correctly named
  1290.  
  1291. #Video (and audio)
  1292.     if cmd in ("mplayer", "mwrap", "vlc", "gmplayer", "smplayer", "mencoder", "kmplayer", "Parole", "whaawmp", "dragonplayer","ffmpeg"):
  1293.         return re.search("\.(mkv|m4v|mpe?g|avi|mp.|wmv|rmvb|as[fx]|divx|vob|ogm|rm|flv|part|iso|mp?|ogg|wav|flac|m4a)$",file,re.I) != None
  1294.  
  1295. #Audio
  1296.     if cmd in ("mpg123", "mpg123s", "mpg321", "mp3blaster", "cmus", "cplay", "moc", "xmms", "xmms2", "sonata", "deadbeef","ogg123","mnama"):
  1297.         return re.search("\.(mp.|aac|wav|ogg|gsm|dct|flac|au|aiff|vox|wma|aac|ra|m4a)$",file,re.I) != None
  1298.  
  1299. #PDF
  1300.     if cmd in ("xpdf","epdfview","evince","foxit","mupdf","okular","apvlv","zathura"):
  1301.         return re.search("\.pdf$",file,re.I) != None
  1302.  
  1303. #Images
  1304.     if cmd in ("feh","geeqie","gqview","eog","gpicview","gthumb","mirage","qiv","ristretto","xnview","xv","viewnior"):
  1305.         return re.search("\.(jpe?g|png|gif|tiff|bmp|ico?n|tif)$",file,re.I) != None
  1306.  
  1307. #Games
  1308.     if cmd in ("sdlmame","openmsx","msxplay","zsnes","desmume","VirtualBoy"):
  1309.         return re.search("\.(rom|dsk)$",file,re.I) != None
  1310.  
  1311. #Wine
  1312.     if cmd in ("wine", "winewrap", "wineconsole"):
  1313.         return re.search("\.(exe|com|bat)$", file, re.I) != None
  1314.  
  1315. #Archives
  1316.     if cmd in ("atool","x","xi","gunzip","extract","unzip","unrar","zip","rar","7z", "comix", "v"):
  1317.         return re.search("\.(tgz|zip|rar|bz2|gz|tar|exe|pk3|lha|Z|lzma)$",file,re.I) != None
  1318.  
  1319. #Text
  1320.     if cmd in ("vim","nano","acme","beaver","geany","leafpad","medit","mousepad","pyroom","sam","vi","gvim","emacs","tea","scite"):
  1321.         return re.search("\.(pdf|rom|dsk|jpe?g|png|gif|tiff|bmp|ico?n|tif|pdf|wav|ogg|gsm|dct|flac|au|aiff|vox|wma|aac|ra|mkv|mpe?g|avi|mp4|wmv|rmvb|as[fx]|divx|vob|ogm|rm|flv|part|iso|mp.|tgz|zip|rar|bz2|gz|tar|exe|pk3|lha|Z|lzma|\.o)$",file,re.I) == None and not isdir #everything else
  1322.  
  1323.     return False
  1324.  
  1325. main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement