Skip to content

Commit 1e4b44e

Browse files
Ken Odegardjonathanslenders
authored andcommitted
Advanced tempfile
Introduced a new tempfile argument to Buffer which allows the user to provide a partial path in order to enable more advanced syntax highlighting (e.g. git commit message highlighting).
1 parent d3ad16d commit 1e4b44e

File tree

2 files changed

+51
-9
lines changed

2 files changed

+51
-9
lines changed

prompt_toolkit/buffer.py

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import os
77
import re
88
import shlex
9+
import shutil
910
import subprocess
1011
import tempfile
1112
from asyncio import Task, ensure_future
@@ -167,6 +168,10 @@ class Buffer:
167168
"open in editor" function. For a Python REPL, this would be ".py", so
168169
that the editor knows the syntax highlighting to use. This can also be
169170
a callable that returns a string.
171+
:param tempfile: For more advanced tempfile situations where you need
172+
control over the subdirectories and filename. For a Git Commit Message,
173+
this would be ".git/COMMIT_EDITMSG", so that the editor knows the syntax
174+
highlighting to use. This can also be a callable that returns a string.
170175
:param name: Name for this buffer. E.g. DEFAULT_BUFFER. This is mostly
171176
useful for key bindings where we sometimes prefer to refer to a buffer
172177
by their name instead of by reference.
@@ -213,6 +218,7 @@ def __init__(self,
213218
history: Optional[History] = None,
214219
validator: Optional[Validator] = None,
215220
tempfile_suffix: Union[str, Callable[[], str]] = '',
221+
tempfile: Union[str, Callable[[], str]] = '',
216222
name: str = '',
217223
complete_while_typing: FilterOrBool = False,
218224
validate_while_typing: FilterOrBool = False,
@@ -238,6 +244,7 @@ def __init__(self,
238244
self.auto_suggest = auto_suggest
239245
self.validator = validator
240246
self.tempfile_suffix = tempfile_suffix
247+
self.tempfile = tempfile
241248
self.name = name
242249
self.accept_handler = accept_handler
243250

@@ -1398,7 +1405,30 @@ def open_in_editor(self, validate_and_handle: bool = False) -> 'asyncio.Task[Non
13981405
raise EditReadOnlyBuffer()
13991406

14001407
# Write to temporary file
1401-
descriptor, filename = tempfile.mkstemp(to_str(self.tempfile_suffix))
1408+
if self.tempfile:
1409+
# Try to make according to tempfile logic.
1410+
head, tail = os.path.split(to_str(self.tempfile))
1411+
if os.path.isabs(head):
1412+
head = head[1:]
1413+
1414+
dirpath = tempfile.mkdtemp()
1415+
remove = dirpath
1416+
if head:
1417+
dirpath = os.path.join(dirpath, head)
1418+
# Assume there is no issue creating dirs in this temp dir.
1419+
os.makedirs(dirpath)
1420+
1421+
# Open the filename of interest.
1422+
filename = os.path.join(dirpath, tail)
1423+
descriptor = os.open(filename, os.O_WRONLY|os.O_CREAT)
1424+
else:
1425+
# Fallback to tempfile_suffix logic.
1426+
suffix = None
1427+
if self.tempfile_suffix:
1428+
suffix = to_str(self.tempfile_suffix)
1429+
descriptor, filename = tempfile.mkstemp(suffix)
1430+
remove = filename
1431+
14021432
os.write(descriptor, self.text.encode('utf-8'))
14031433
os.close(descriptor)
14041434

@@ -1430,8 +1460,8 @@ async def run() -> None:
14301460
self.validate_and_handle()
14311461

14321462
finally:
1433-
# Clean up temp file.
1434-
os.remove(filename)
1463+
# Clean up temp dir/file.
1464+
shutil.rmtree(remove)
14351465

14361466
return get_app().create_background_task(run())
14371467

prompt_toolkit/shortcuts/prompt.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,8 @@ class PromptSession(Generic[_T]):
334334
'validate_while_typing', 'complete_style', 'mouse_support',
335335
'auto_suggest', 'clipboard', 'validator', 'refresh_interval',
336336
'input_processors', 'enable_system_prompt', 'enable_suspend',
337-
'enable_open_in_editor', 'reserve_space_for_menu', 'tempfile_suffix')
337+
'enable_open_in_editor', 'reserve_space_for_menu', 'tempfile_suffix',
338+
'tempfile')
338339

339340
def __init__(
340341
self,
@@ -373,7 +374,8 @@ def __init__(
373374
input_processors: Optional[List[Processor]] = None,
374375
key_bindings: Optional[KeyBindingsBase] = None,
375376
erase_when_done: bool = False,
376-
tempfile_suffix: str = '.txt',
377+
tempfile_suffix: Optional[Union[str, Callable[[], str]]] = '.txt',
378+
tempfile: Optional[Union[str, Callable[[], str]]] = None,
377379

378380
refresh_interval: float = 0,
379381

@@ -425,6 +427,7 @@ def __init__(
425427
self.enable_open_in_editor = enable_open_in_editor
426428
self.reserve_space_for_menu = reserve_space_for_menu
427429
self.tempfile_suffix = tempfile_suffix
430+
self.tempfile = tempfile
428431

429432
# Create buffers, layout and Application.
430433
self.history = history
@@ -480,7 +483,8 @@ def accept(buff: Buffer) -> bool:
480483
history=self.history,
481484
auto_suggest=DynamicAutoSuggest(lambda: self.auto_suggest),
482485
accept_handler=accept,
483-
tempfile_suffix=lambda: self.tempfile_suffix)
486+
tempfile_suffix=lambda: self.tempfile_suffix,
487+
tempfile=lambda: self.tempfile)
484488

485489
def _create_search_buffer(self) -> Buffer:
486490
return Buffer(name=SEARCH_BUFFER)
@@ -795,7 +799,8 @@ def prompt(
795799
enable_system_prompt: Optional[FilterOrBool] = None,
796800
enable_suspend: Optional[FilterOrBool] = None,
797801
enable_open_in_editor: Optional[FilterOrBool] = None,
798-
tempfile_suffix: Optional[str] = None,
802+
tempfile_suffix: Optional[Union[str, Callable[[], str]]] = None,
803+
tempfile: Optional[Union[str, Callable[[], str]]] = None,
799804

800805
# Following arguments are specific to the current `prompt()` call.
801806
default: Union[str, Document] = '',
@@ -910,6 +915,8 @@ class itself. For these, passing in ``None`` will keep the current
910915
self.enable_open_in_editor = enable_open_in_editor
911916
if tempfile_suffix is not None:
912917
self.tempfile_suffix = tempfile_suffix
918+
if tempfile is not None:
919+
self.tempfile = tempfile
913920

914921
self._add_pre_run_callables(pre_run, accept_default)
915922
self.default_buffer.reset(default if isinstance(default, Document) else Document(default))
@@ -958,7 +965,8 @@ async def prompt_async(
958965
enable_system_prompt: Optional[FilterOrBool] = None,
959966
enable_suspend: Optional[FilterOrBool] = None,
960967
enable_open_in_editor: Optional[FilterOrBool] = None,
961-
tempfile_suffix: Optional[str] = None,
968+
tempfile_suffix: Optional[Union[str, Callable[[], str]]] = None,
969+
tempfile: Optional[Union[str, Callable[[], str]]] = None,
962970

963971
# Following arguments are specific to the current `prompt()` call.
964972
default: Union[str, Document] = '',
@@ -1033,6 +1041,8 @@ async def prompt_async(
10331041
self.enable_open_in_editor = enable_open_in_editor
10341042
if tempfile_suffix is not None:
10351043
self.tempfile_suffix = tempfile_suffix
1044+
if tempfile is not None:
1045+
self.tempfile = tempfile
10361046

10371047
self._add_pre_run_callables(pre_run, accept_default)
10381048
self.default_buffer.reset(default if isinstance(default, Document) else Document(default))
@@ -1207,7 +1217,8 @@ def prompt(
12071217
enable_system_prompt: Optional[FilterOrBool] = None,
12081218
enable_suspend: Optional[FilterOrBool] = None,
12091219
enable_open_in_editor: Optional[FilterOrBool] = None,
1210-
tempfile_suffix: Optional[str] = None,
1220+
tempfile_suffix: Optional[Union[str, Callable[[], str]]] = None,
1221+
tempfile: Optional[Union[str, Callable[[], str]]] = None,
12111222

12121223
# Following arguments are specific to the current `prompt()` call.
12131224
default: str = '',
@@ -1255,6 +1266,7 @@ def prompt(
12551266
enable_suspend=enable_suspend,
12561267
enable_open_in_editor=enable_open_in_editor,
12571268
tempfile_suffix=tempfile_suffix,
1269+
tempfile=tempfile,
12581270
default=default,
12591271
accept_default=accept_default,
12601272
pre_run=pre_run)

0 commit comments

Comments
 (0)