Call a text-editor from within a Python script to get user input

Motivation

I'd like to know how integrate the use of text editors (e.g. emacs, vim, etc.) into command-line Python programs.

Features

I'd like to be able to do the following:

• Call a text editor from within a program in order to edit a file or buffer

• Regain control after the text editor exits

• Use temporary files, i.e. don't leave anything lying around after the program exits

• Prepopulate the file or buffer with data

• Get the edited text back for use in the program

Implementation

I wrote a small module named callvim.py (betraying my preferred choice of text editor) which creates a temporary file, populates it with some text, opens it with a text editor (defaults to vim), and prints the modified text:

#!/usr/bin/env python
# -*- encoding: ascii -*-
"""callvim.py

Demonstrates calling a text-editor (e.g. Vim) from within a Python script,
including passing input to the editor and reading output from the editor.
"""

import tempfile
import os
from subprocess import call

# Get the text editor from the shell, otherwise default to Vim
EDITOR = os.environ.get('EDITOR','vim')

# Set initial input with which to populate the buffer
initial_message = "Hello world!"

# Open a temporary file to communicate through (tempfile should avoid any filename conflicts)
#
# NOTE: Don't autodelete the file on close!
#       We want to reopen the file incase the editor uses a swap-file.
#
with tempfile.NamedTemporaryFile(suffix=".tmp", delete=False) as tf:

# Write the initial content to the file I/O buffer
tf.write(initial_message)

# Flush the I/O buffer to make sure the data is written to the file
tf.flush()

# Open the file with the text editor
call([EDITOR, tf.name])

# Reopen the file to read the edited data
with open(tf.name, 'r') as tf:

# Read the file data into a variable

# Output the data
print(edited_message)


If I read it correctly, your edited_message would also contain your initial_message, is this expected?

I think this could be fixed by wrapping your input message this way in the file :

<Input message>

Input above this line will be ignored
###########################

<end of file>


You could then do :

edited_message = tf.read()
start_input_index = edited_message.find("###########################")

# Use the +2 to account for the \n
edited_message = edited_message[start_input_index + 2 :]


Maybe this exact code wouldn't work, I didn't test it, but the main idea is there, it would make more sense to your code.

• Why should it contain the initial message? It is up to the user to keep it or remove it. It won't stay for sure. – 301_Moved_Permanently Nov 30 '19 at 18:03
• @409_Conflict I'm not sure I get your point, are you saying you think it shouldn't be automatically removed because the user could remove it themself or that it should be removed because the user would remove it anyways? – IEatBagels Dec 2 '19 at 15:05
• I’m just saying that, since the user can edit the file to its heart content, you can't make any assumption about what the edited_message string will contain. So your find may return -1 making you skip the first character of valid input. If you want to support the "ignored above that mark" feature, you may have better results using edited_message.split("###########################\n", 1)[-1]. – 301_Moved_Permanently Dec 2 '19 at 16:32
• Thanks for your submission! However, as @409_Conflict said in their comment, I wasn't really looking to parse the edited text - just retrieve it. – igal Dec 4 '19 at 3:08

click is a great library for command line processing and it has some utilities, click.edit() is portable and uses the EDITOR environment variable. I typed the line, stuff, into the editor. Notice it is returned as a string. Nice.

So, rather then write any code, you could consider replacing what your working on with these two lines:

(venv) /tmp/editor $export EDITOR='=mvim -f' (venv) /tmp/editor$ python
>>> import click
>>> click.edit()
'stuff\n'


Check out the docs https://click.palletsprojects.com/en/7.x/utils/#launching-editors My entire experience:

/tmp $mkdir editor /tmp$ cd editor
/tmp/editor $python3 -m venv venv /tmp/editor$ source venv/bin/activate
(venv) /tmp/editor $pip install click Collecting click Using cached https://files.pythonhosted.org/packages/fa/37/45185cb5abbc30d7257104c434fe0b07e5a195a6847506c074527aa599ec/Click-7.0-py2.py3-none-any.whl Installing collected packages: click Successfully installed click-7.0 You are using pip version 19.0.3, however version 19.3.1 is available. You should consider upgrading via the 'pip install --upgrade pip' command. (venv) /tmp/editor$ export EDITOR='=mvim -f'
(venv) /tmp/editor \$ python
Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 16:52:21)
[Clang 6.0 (clang-600.0.57)] on darwin