Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/scripts/shell_completions.py
Views: 687
#!/usr/bin/env python1###############################################################################2#3# CoCalc: Collaborative Calculation4#5# Copyright (C) 2016, Sagemath Inc.6#7# This program is free software: you can redistribute it and/or modify8# it under the terms of the GNU General Public License as published by9# the Free Software Foundation, either version 3 of the License, or10# (at your option) any later version.11#12# This program is distributed in the hope that it will be useful,13# but WITHOUT ANY WARRANTY; without even the implied warranty of14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the15# GNU General Public License for more details.16#17# You should have received a copy of the GNU General Public License18# along with this program. If not, see <http://www.gnu.org/licenses/>.19#20###############################################################################2122# Known bugs/oddities:23# [ ] Fails to correctly detect the prompt with certain .bashrc settings.24# [X] Spurious empty strings/escape sequences appear with multi-page output.25# [ ] Output is not consistent. Example: 'git stat' returns 'git status', while26# 'git sta' returns a list of completions only for the 'sta' part of the27# command.2829import argparse, pexpect, sys3031COMPLETIONS_COMMAND = ". /etc/bash_completion"32BIGLIST_WARNING = "(y or n)"33NEXT_PAGE_INDICATOR = "--More--"34DEFAULT_TIMEOUT = 235DEFAULT_SHELL = "bash"363738def print_string(message, string):39print message + " \"" + string + "\""404142def completions(partial_command,43shell=DEFAULT_SHELL,44return_raw=False,45import_completions=True,46get_prompt=True,47prompt="$ ",48timeout=DEFAULT_TIMEOUT,49biglist=True,50verbose=False,51logfile=None):52"""53Returns a list containing the tab completions found by the shell for the54input string.55"""5657child = pexpect.spawn(shell, timeout=timeout)5859if verbose:60child.logfile = sys.stdout6162if logfile is not None:63logfile = open(logfile, "w")64child.logfile = logfile6566# We want to echo characters sent to the shell so that new prompts print67# on their own line.68child.setecho(True)6970# echo_on = child.getecho()71# if verbose:72# print "Echo state: " + str(echo_on)7374# Get a bare command prompt in order to find the end of the75# list of completions.76if get_prompt:7778# !!!79# Here we assume that the shell will only print out a command80# prompt on startup. This is not always true.81# !!!82child.sendline()83child.expect_exact("\r\n")84prompt = child.before8586# We just hit enter, so we expect the shell to print a new prompt and87# we need to clear it out of the buffer.88child.expect_exact(prompt)8990if verbose:91print_string("Prompt:", prompt)9293# Run a script to configure extra bash completions.94if import_completions:95child.sendline(COMPLETIONS_COMMAND)96child.expect_exact(prompt)9798child.send(partial_command + "\t\t")99child.expect_exact(partial_command)100#### NOTE: I don't understand why this time we don't get an echo.101#### New idea: Of course... it's only echoing the sent characters.102# child.expect_exact(partial_command)103104index = child.expect_exact([" ", "\r\n", pexpect.TIMEOUT])105106if index == 0:107# Bash found a single completion and filled it in.108return [partial_command + child.before]109110elif index == 1:111index = child.expect_exact(112[BIGLIST_WARNING, NEXT_PAGE_INDICATOR, prompt])113if index == 0 or index == 1:114# The shell found too many completions to list on one screen.115if biglist:116completions = ""117118# For very long lists the shell asks whether to continue.119if index == 0:120child.send("y")121122# Shorter lists print to the screen without asking.123else:124completions += child.before125child.send(" ")126127# Keep sending space to get more pages until we get back to the128# command prompt.129while True:130index = child.expect_exact([NEXT_PAGE_INDICATOR, prompt])131completions += child.before132if index == 0:133child.send(" ")134elif index == 1:135break136137# Remove spurious escape sequence.138completions = completions.replace("\x1b[K", "")139140elif index == 2:141# Bash found more than one completion and listed them on multiple lines.142# child.expect_exact(prompt)143completions = child.before144145elif index == 2:146# If the command timed out, either no completion was found or it147# found a single completion witout adding a space (for instance, this148# happens when completing the name of an executable).149150# print_string("Timed out:", child.before)151152# Remove any bell characters the shell appended to the completion.153return [partial_command + child.buffer.replace("\x07", "")]154155child.close()156157# Parse the completions into a Python list of strings.158return completions.split()159160161if __name__ == "__main__":162163parser = argparse.ArgumentParser(164description=165"Returns the tab completions found by the shell for the input string.")166167parser.add_argument(168"COMMAND",169type=str,170help="The partial command that the shell should attempt to complete.")171172parser.add_argument(173"--no_biglists",174action="store_false",175default=True,176help="Abort execution if the shell finds a large number of completions."177)178179parser.add_argument(180"--no_detect_prompt",181action="store_false",182default=True,183help=184"Don't attempt to detect the command prompt, and use a built-in constant instead. This should speed up execution times."185)186187parser.add_argument(188"--no_import_completions",189default=True,190help=191"Don't set up completions by running the script at /etc/completions.")192parser.add_argument(193"--raw",194action="store_false",195default=False,196help="Returns all output from the shell without formatting changes.")197198parser.add_argument(199"--separator",200"-s",201default="\n",202help="Character used to separate the list of completions.")203204parser.add_argument(205"--shell",206default="bash",207help="The shell to query for completions. Defaults to bash.")208209parser.add_argument(210"--timeout",211"-t",212metavar="SECONDS",213type=float,214default=DEFAULT_TIMEOUT,215help="The time in seconds before the program detects no shell output.")216217parser.add_argument(218"--verbose",219"-v",220action="store_true",221default=False,222help="Verbose mode.")223224parser.add_argument(225"--log",226"-l",227metavar="LOGFILE",228default=None,229help="Log all shell output to file named LOGFILE.")230231args = parser.parse_args()232233completion_list = completions(234args.COMMAND,235verbose=args.verbose,236return_raw=args.raw,237get_prompt=args.no_detect_prompt,238timeout=args.timeout,239biglist=args.no_biglists,240logfile=args.log)241242print str(args.separator).join(completion_list)243244245