Skip to content

Commit e5b9eff

Browse files
committed
feat(command): open external editor command in composing message
OPEN_EXTERNAL_EDITOR command paste the composing message in a python tempfile, run external editor over it and wait the exit before update the message. $ZULIP_EDITOR_COMMAND and fallback $EDITOR are use for the external editor command. Use shlex to split command.
1 parent bef2a8f commit e5b9eff

File tree

4 files changed

+67
-0
lines changed

4 files changed

+67
-0
lines changed

docs/FAQ.md

+30
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
- [When are messages marked as having been read?](#when-are-messages-marked-as-having-been-read)
1414
- [How do I access multiple servers?](#how-do-i-access-multiple-servers)
1515
- [What is autocomplete? Why is it useful?](#what-is-autocomplete-why-is-it-useful)
16+
- [Can I compose messages in another editor?](#can-i-compose-messages-in-another-editor)
1617
- Something is not working!
1718
- [Colors appear mismatched, don't change with theme, or look strange](#colors-appear-mismatched-dont-change-with-theme-or-look-strange)
1819
- [Symbols look different to in the provided screenshots, or just look incorrect](#symbols-look-different-to-in-the-provided-screenshots-or-just-look-incorrect)
@@ -372,6 +373,35 @@ through autocomplete depend upon the context automatically.
372373
**NOTE:** If a direct message recipient's name contains comma(s) (`,`), they
373374
are currently treated as comma-separated recipients.
374375

376+
## Can I compose messages in another editor?
377+
378+
In the main branch of zulip-terminal, you can now use an external editor to
379+
compose your message using `ctrl o` shortcut. If `ZULIP_EDITOR_COMMAND` or
380+
`EDITOR` environment variable is set, this command or program would be used
381+
to open the message by appending a temporary file filepath of the current message.
382+
383+
It will work directly for most terminal editors with only the program name `vim`,
384+
`nano`, `helix`, `kakoune`, `nvim`...
385+
386+
It can also be used for desktop editor with some constraint which needs to be
387+
address using `ZULIP_EDITOR_COMMAND` custom command. The program must not fork
388+
or detach from the running terminal and should open in a new window, some
389+
examples:
390+
391+
- [lapce](https://github.com/lapce/lapce) with `lapce -n -w`
392+
- [sublime-text](https://www.sublimetext.com/) with `subl -n -w`
393+
- [marker](https://github.com/fabiocolacio/Marker) with `marker`
394+
- [vim](https://github.com/vim/vim) with `vim -g -f` or `gvim -f`
395+
- [vscode](https://github.com/microsoft/vscode) with `code -n -w`
396+
397+
When the external editor process ends (closing the window or quitting terminal
398+
editor), the composing box will be updated with the new message content from
399+
the temporary file.
400+
401+
**NOTE:** Backslashing white space (`\ `) is needed when using an executable
402+
containing them, for example for Sublime Text on macOS can be configure with
403+
`/Applications/Sublime\ Text.app/Contents/SharedSupport/bin/subl`.
404+
375405
## Colors appear mismatched, don't change with theme, or look strange
376406

377407
Some terminal emulators support specifying custom colors, or custom color

docs/hotkeys.md

+1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
|Autocomplete @mentions, #stream_names, :emoji: and topics|<kbd>ctrl</kbd> + <kbd>f</kbd>|
8383
|Cycle through autocomplete suggestions in reverse|<kbd>ctrl</kbd> + <kbd>r</kbd>|
8484
|Narrow to compose box message recipient|<kbd>meta</kbd> + <kbd>.</kbd>|
85+
|Open the message in external editor|<kbd>ctrl</kbd> + <kbd>o</kbd>|
8586
|Jump to the beginning of line|<kbd>ctrl</kbd> + <kbd>a</kbd>|
8687
|Jump to the end of line|<kbd>ctrl</kbd> + <kbd>e</kbd>|
8788
|Jump backward one word|<kbd>meta</kbd> + <kbd>b</kbd>|

zulipterminal/config/keys.py

+5
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,11 @@ class KeyBinding(TypedDict):
315315
'help_text': 'View user information (From Users list)',
316316
'key_category': 'general',
317317
},
318+
'OPEN_EXTERNAL_EDITOR': {
319+
'keys': ['ctrl o'],
320+
'help_text': 'Open the message in external editor',
321+
'key_category': 'msg_compose',
322+
},
318323
'BEGINNING_OF_LINE': {
319324
'keys': ['ctrl a'],
320325
'help_text': 'Jump to the beginning of line',

zulipterminal/ui_tools/boxes.py

+31
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@
22
UI boxes for entering text: WriteBox, MessageSearchBox, PanelSearchBox
33
"""
44

5+
import os
56
import re
7+
import shlex
8+
import shutil
9+
import subprocess
610
import unicodedata
711
from collections import Counter
812
from datetime import datetime, timedelta
13+
from tempfile import NamedTemporaryFile
914
from time import sleep
1015
from typing import Any, Callable, Dict, List, NamedTuple, Optional, Tuple
1116

@@ -808,6 +813,32 @@ def keypress(self, size: urwid_Size, key: str) -> Optional[str]:
808813
elif is_command_key("MARKDOWN_HELP", key):
809814
self.view.controller.show_markdown_help()
810815
return key
816+
elif is_command_key("OPEN_EXTERNAL_EDITOR", key):
817+
editor = os.environ.get("ZULIP_EDITOR_COMMAND", os.environ.get("EDITOR"))
818+
if editor is None:
819+
self.view.controller.report_error(
820+
"Configure $EDITOR or $ZULIP_EDITOR_COMMAND shell environment."
821+
)
822+
return key
823+
editor_splits = shlex.split(editor)
824+
fullpath_program = shutil.which(editor_splits[0])
825+
if fullpath_program is None:
826+
self.view.controller.report_error(
827+
"Editor program not found, check $EDITOR "
828+
"or $ZULIP_EDITOR_COMMAND."
829+
)
830+
return key
831+
editor_splits[0] = fullpath_program
832+
with NamedTemporaryFile(suffix=".md") as edit_tempfile:
833+
with open(edit_tempfile.name, mode="w") as edit_writer:
834+
edit_writer.write(self.msg_write_box.edit_text)
835+
self.view.controller.loop.screen.stop()
836+
editor_splits.append(edit_tempfile.name)
837+
subprocess.call(editor_splits)
838+
with open(edit_tempfile.name, mode="r") as edit_reader:
839+
self.msg_write_box.edit_text = edit_reader.read().rstrip()
840+
self.view.controller.loop.screen.start()
841+
return key
811842
elif is_command_key("SAVE_AS_DRAFT", key):
812843
if self.msg_edit_state is None:
813844
if self.compose_box_status == "open_with_private":

0 commit comments

Comments
 (0)