How to put text in input line: how to ask for user input on the command line while providing a 'default' answer that the user can edit or delete?
Asked Answered
V

4

20

I am creating a Python script that asks for input from the command line. The user will have the ability to edit a part of a file. I can ask for the new information and overwrite it in the file, no problem. But I would rather have the to-edit part of the file already put in the command line, so it does not have to be typed completely over. Is this possible?

File:

1|This file
2|is not empty

Example:

>>>edit line 2
Fetching line 2
Edit the line then hit enter
>>>is not empty                  #This is written here by the script, not by the user

Which then can be changed to

>>>is not full either
Edited file

Afther which the file has changed to:

1|This file
2|is not full either

I hope it's clear what I am trying to accomplish.

This question has been said to answer my question, it does to a certain extent. It does when I am running Linux with readline. However, I am not. I am using Windows and am not using readline. I would like to only use the standard library.
An answer for Windows is also provided with that question. However, I get an ImportError with win32console, it might be because mentioned question is not about Python3.4, but mine is. Also, I was wondering if this was possible with the standard library, not with an external library.

Vane answered 12/5, 2015 at 14:31 Comment(11)
So the question is: How to ask for user input on the command line while providing a 'default' answer that the user can edit or delete?Reliable
I can't say for sure that this is impossible, but in all my years, I've never seen anything in the standard libraries that could do this. (unless it's in an OS-specific module like curses)Ilmenite
@Ilmenite Not sure what you mean with curses, but I do not use any different libraries than the default.Vane
One possible alternative is to write your own CLI shell using a GUI library, which you can then configure to do whatever you want. May be more trouble than it's worth, though, since you'd basically be implementing all behavior from the ground up.Ilmenite
See e.g. https://mcmap.net/q/237908/-show-default-value-for-editing-on-python-input-possible/3001761Subdual
@Subdual That does not answer my question, unfortunately. See my edit.Vane
@Vane there is a Windows answer there. If that doesn't do it for you, maybe it isn't possible in the standard library and you need to make some compromises.Subdual
@Subdual I just found it, yes. I cannot seem to find the win32console import. Maybe it is only Python2.x?Vane
@Vane ah; it's not in the standard library: https://mcmap.net/q/244884/-no-module-named-win32console-while-running-wexpect/3001761 (note you can check what's in the standard lib at e.g. docs.python.org/2/library/index.html)Subdual
@Subdual Thank you for providing the link. I will not use it though, but it will prove itself useful in the future, I am sure.Vane
Have you seen https://mcmap.net/q/49660/-how-to-generate-keyboard-events?Alan
S
7

Unfortunately, I don't know if kind of input() with default value is available in standard library.

There is an external solution - use win32console as mentioned in this answer. However, it has two pitfalls as far as I can see. First, the import is bundled in a package pywin32. So you would use pip install pywin32, except it does not work, because of the second pitfall: the information about the package at pypi is outdated, it says that package is incompatible with Python 3.4...

But in fact, it can work! You should follow the "Download URL" visible at pypi project page (i.e. https://sourceforge.net/projects/pywin32/files/pywin32/ ) and install latest build. I just installed build 219 for Py3.4, as I myself also use this Python version. On the page installers are provided for several Python versions for 32bit and 64bit Windows.

Also, I've tweaked the code from above-linked SO answer to work in Python 3:

import win32console

_stdin = win32console.GetStdHandle(win32console.STD_INPUT_HANDLE)

def input_def(prompt, default=''):
    keys = []
    for c in str(default):
        evt = win32console.PyINPUT_RECORDType(win32console.KEY_EVENT)
        evt.Char = c
        evt.RepeatCount = 1
        evt.KeyDown = True
        keys.append(evt)

    _stdin.WriteConsoleInput(keys)
    return input(prompt)

if __name__ == '__main__':
    name = input_def('Folder name: ', 'it works!!!')
    print()
    print(name)

This works on my Windows machine... If this does not work on yours, can you provide the error message?

Saba answered 22/8, 2015 at 12:42 Comment(4)
OP asks for stdlib solution (in principle, you could make the calls using ctypes module). If we are installing 3-rd party libraries; I would try pyreadline with this answer (and win-unicode-console to support arbitrary Unicode input/output in the console).Ixtle
From the wording in the question, I think OP simply "wonders" if there is a stdlib solution, but asks for any working solution :) Package readline is unavailable on Windows, and replacement package pyreadline is not in the core. So is win-unicode-console. Nevertheless, I've just tested pyreadline because it seemed simpler, but this gist does not work in my case.Saba
this code sometimes adds random characters at the beginning of my default string, any idea why? (I guess the culprit is evt = win32console.PyINPUT_RECORDType(win32console.KEY_EVENT)Notional
Actually the keys variable seems fine so I guess the problem is with _stdin.WriteConsoleInput(keys)Notional
K
1

I have written a line editor which hopefully does what you are looking for. But it is a quick-and-dirty hack. It is Windows only and written with CPython 3.6.5 on Windows 10, so its use might be limited. It has been tested on codepage 1252 (ANSI Latin 1; Western European (Windows)) and codepage 65001 (utf-8). It is very basic and a bit sluggish as it is not speed-optimized. (I should rewrite it in C but I do not have the time.) It is hardly tested and poorly documented.

import msvcrt
import os
import sys

if os.name != 'nt':
    raise NotImplementedError('This module works only on MS Windows!')

CTRL_00         = 0
CTRL_E0         = 224
KEY_BACKSPACE   = 8
KEY_DELETE      = 83                                            # CTRL
KEY_END         = 79                                            # CTRL
KEY_ESC         = 27
KEY_HOME        = 71                                            # CTRL
KEY_INSERT      = 82                                            # CTRL
KEY_LEFT        = 75                                            # CTRL
KEY_RETURN      = 13
KEY_RIGHT       = 77                                            # CTRL

flush = sys.stdout.flush
write = sys.stdout.write

mode    = ('[OVR]> ', '[INS]> ')                                # overwrite, insert
prefix  = len(mode[0])

def _update_line(insert, source, length, line, target):
    """Write a new line and position the cursor.
    source: previous cursor position
    length: old line length
    line:   edited line
    target: next cursor position
    """
    write('\b' * source)                                        # set cursor to start of line
    write(' ' * length)                                         # erase old line
    write('\b' * length)                                        # again, set cursor to start of line
    write(mode[insert] + line[prefix:])                         # write updated line
    write('\b' * (len(line) - target))                          # set cursor to new position
    flush()                                                     # write buffer to screen

def mswin_line_edit(default_string, insert=True):
    """Edit a MS Windows CLI line."""

    insert = insert
    line = mode[insert] + default_string
    count = len(line)
    before = line[:count]
    after = line[count:]
    print(line, end='', flush=True)
    cursor = count

    while True:
        key = msvcrt.getwch()
        num = ord(key)
        if num == KEY_ESC:                                      # abort edit
            return default_string
        if num == KEY_RETURN:                                   # finish edit
            return line
        if num == KEY_BACKSPACE:                                # delete character before cursor
            if cursor > prefix:
                before = line[:cursor - 1]
                after = line[cursor:]
                line = before + after
                _update_line(insert, cursor, count, line, cursor - 1)
                cursor -= 1
                count = len(line)
        elif num == CTRL_E0 or num == CTRL_00:                  # CTRL
            ctrl = ord(msvcrt.getwch())
            if ctrl == KEY_END:                                     # set cursor after last character
                if cursor < count:
                    before = line
                    after = ''
                    _update_line(insert, cursor, count, line, count)
                    cursor = count
            elif ctrl == KEY_HOME:                                  # set cursor before first character
                if cursor > prefix:
                    before = ''
                    after = line
                    _update_line(insert, cursor, count, line, prefix)
                    cursor = prefix
            elif ctrl == KEY_LEFT:                                  # move cursor 1 character to the left
                if cursor > prefix:
                    before = line[:cursor]
                    after = line[cursor:]
                    _update_line(insert, cursor, count, line, cursor - 1)
                    cursor -= 1
            elif ctrl == KEY_RIGHT:                                 # move cursor 1 character to the right
                if cursor < count:
                    before = line[:cursor]
                    after = line[cursor:]
                    _update_line(insert, cursor, count, line, cursor + 1)
                    cursor += 1
            elif ctrl == KEY_DELETE:                                # delete character after cursor
                if cursor < count:
                    before = line[:cursor]
                    after = line[cursor + 1:]
                    line = before + after
                    _update_line(insert, cursor, count, line, cursor)
                    count = len(line)
            elif ctrl == KEY_INSERT:                                # switch insert/overwrite mode
                insert ^= True
                _update_line(insert, cursor, count, line, cursor)
        else:                                                   # ordinary character
            before = line[:cursor] + key
            if insert:
                after = line[cursor:]
            else:
                after = line[cursor + 1:]
            line = before + after
            _update_line(insert, cursor, count, line, cursor + 1)
            cursor += 1
            count = len(line)

if __name__ == '__main__':
    test_string = input('test string: ')
    result = mswin_line_edit(test_string)
    print(f'\n{result}')
Karee answered 13/4, 2018 at 21:43 Comment(2)
Too bad, it doesn't work out-of-the-box (with some minimal adjustments) on a Mac. Perhaps you could adjust the "universal getch answer in Python read a single character from the user?Posit
I apologize. I very seldom use Linux (Unix) and I don't have any access to a Mac. So I'm not able to write--and especially test-- an OS-agnostic version.Karee
A
0

You could do it with tkinter:

from tkinter import *
def enter():
    global commandEntry
    command = commandEntry.get()
    # Do stuff with command
    commandEntry.delete(0, END)
def edit_line(line):
    global commandEntry
    commandEntry.insert(0, line)
root = Tk()
messageVar = StringVar()
messageVar.set("Enter a command:")
message = Label(root, textvariable=messageVar)
commandEntry = Entry(root)
enterButton = Button(root, text="Enter", command=enter)
root.mainloop()
Aversion answered 24/8, 2015 at 21:0 Comment(0)
S
-1

You should just have 2 variables: one for standard string, one for string that will user change by itself. Like:

str1 = 'String that is standard'
str2 = str1 #it usually will be standard string
usr = input('your text goes here')
if len(usr) != 0:
    str2 = usr
#and here goes code for writing string into file
Spiv answered 22/5, 2015 at 12:1 Comment(1)
This may solve the issue of having a default value, but it does not allow the user to edit the default, which I think was more central to the question.Racquelracquet

© 2022 - 2024 — McMap. All rights reserved.