Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 65 additions & 65 deletions docs/hotkeys.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,96 +6,96 @@
|Command|Key Combination|
| :--- | :---: |
|Show/hide help menu|<kbd>?</kbd>|
|Show/hide markdown help menu|<kbd>meta</kbd> + <kbd>m</kbd>|
|Show/hide about menu|<kbd>meta</kbd> + <kbd>?</kbd>|
|Go Back|<kbd>esc</kbd>|
|Open draft message saved in this session|<kbd>d</kbd>|
|Redraw screen|<kbd>ctrl</kbd> + <kbd>l</kbd>|
|Quit|<kbd>ctrl</kbd> + <kbd>c</kbd>|
|View user information (From Users list)|<kbd>i</kbd>|
|Show/hide markdown help menu|<kbd>Meta</kbd> + <kbd>M</kbd>|
|Show/hide about menu|<kbd>Meta</kbd> + <kbd>?</kbd>|
|Go Back|<kbd>Esc</kbd>|
|Open draft message saved in this session|<kbd>D</kbd>|
|Redraw screen|<kbd>Ctrl</kbd> + <kbd>L</kbd>|
|Quit|<kbd>Ctrl</kbd> + <kbd>C</kbd>|
|View user information (From Users list)|<kbd>I</kbd>|

## Navigation
|Command|Key Combination|
| :--- | :---: |
|Go up / Previous message|<kbd>up</kbd> / <kbd>k</kbd>|
|Go down / Next message|<kbd>down</kbd> / <kbd>j</kbd>|
|Go left|<kbd>left</kbd> / <kbd>h</kbd>|
|Go right|<kbd>right</kbd> / <kbd>l</kbd>|
|Scroll up|<kbd>page</kbd> + <kbd>up</kbd> / <kbd>K</kbd>|
|Scroll down|<kbd>page</kbd> + <kbd>down</kbd> / <kbd>J</kbd>|
|Go to bottom / Last message|<kbd>end</kbd> / <kbd>G</kbd>|
|Narrow to all messages|<kbd>a</kbd> / <kbd>esc</kbd>|
|Narrow to all direct messages|<kbd>P</kbd>|
|Narrow to all starred messages|<kbd>f</kbd>|
|Go up / Previous message|<kbd>Up ↑</kbd> / <kbd>K</kbd>|
|Go down / Next message|<kbd>Down ↓</kbd> / <kbd>J</kbd>|
|Go left|<kbd>Left ←</kbd> / <kbd>H</kbd>|
|Go right|<kbd>Right →</kbd> / <kbd>L</kbd>|
|Scroll up|<kbd>PgUp</kbd> / <kbd>Shift</kbd> + <kbd>K</kbd>|
|Scroll down|<kbd>PgDn</kbd> / <kbd>Shift</kbd> + <kbd>J</kbd>|
|Go to bottom / Last message|<kbd>End</kbd> / <kbd>Shift</kbd> + <kbd>G</kbd>|
|Narrow to all messages|<kbd>A</kbd> / <kbd>Esc</kbd>|
|Narrow to all direct messages|<kbd>Shift</kbd> + <kbd>P</kbd>|
|Narrow to all starred messages|<kbd>F</kbd>|
|Narrow to messages in which you're mentioned|<kbd>#</kbd>|
|Next unread topic|<kbd>n</kbd>|
|Next unread direct message|<kbd>p</kbd>|
|Perform current action|<kbd>enter</kbd>|
|Next unread topic|<kbd>N</kbd>|
|Next unread direct message|<kbd>P</kbd>|
|Perform current action|<kbd>Enter</kbd>|

## Searching
|Command|Key Combination|
| :--- | :---: |
|Search Users|<kbd>w</kbd>|
|Search Users|<kbd>W</kbd>|
|Search Messages|<kbd>/</kbd>|
|Search Streams|<kbd>q</kbd>|
|Search topics in a stream|<kbd>q</kbd>|
|Search emojis from Emoji-picker popup|<kbd>p</kbd>|
|Search Streams|<kbd>Q</kbd>|
|Search topics in a stream|<kbd>Q</kbd>|
|Search emojis from Emoji-picker popup|<kbd>P</kbd>|

## Message actions
|Command|Key Combination|
| :--- | :---: |
|Reply to the current message|<kbd>r</kbd> / <kbd>enter</kbd>|
|Reply to the current message|<kbd>R</kbd> / <kbd>Enter</kbd>|
|Reply mentioning the sender of the current message|<kbd>@</kbd>|
|Reply quoting the current message text|<kbd>></kbd>|
|Reply directly to the sender of the current message|<kbd>R</kbd>|
|Edit message's content or topic|<kbd>e</kbd>|
|New message to a stream|<kbd>c</kbd>|
|New message to a person or group of people|<kbd>x</kbd>|
|Reply directly to the sender of the current message|<kbd>Shift</kbd> + <kbd>R</kbd>|
|Edit message's content or topic|<kbd>E</kbd>|
|New message to a stream|<kbd>C</kbd>|
|New message to a person or group of people|<kbd>X</kbd>|
|Show/hide Emoji picker popup for current message|<kbd>:</kbd>|
|Narrow to the stream of the current message|<kbd>s</kbd>|
|Narrow to the topic of the current message|<kbd>S</kbd>|
|Narrow to a topic/direct-chat, or stream/all-direct-messages|<kbd>z</kbd>|
|Narrow to the stream of the current message|<kbd>S</kbd>|
|Narrow to the topic of the current message|<kbd>Shift</kbd> + <kbd>S</kbd>|
|Narrow to a topic/direct-chat, or stream/all-direct-messages|<kbd>Z</kbd>|
|Toggle first emoji reaction on selected message|<kbd>=</kbd>|
|Add/remove thumbs-up reaction to the current message|<kbd>+</kbd>|
|Add/remove star status of the current message|<kbd>ctrl</kbd> + <kbd>s</kbd> / <kbd>*</kbd>|
|Show/hide message information|<kbd>i</kbd>|
|Show/hide message sender information|<kbd>u</kbd>|
|Show/hide edit history (from message information)|<kbd>e</kbd>|
|View current message in browser (from message information)|<kbd>v</kbd>|
|Show/hide full rendered message (from message information)|<kbd>f</kbd>|
|Show/hide full raw message (from message information)|<kbd>r</kbd>|
|Add/remove star status of the current message|<kbd>Ctrl</kbd> + <kbd>S</kbd> / <kbd>*</kbd>|
|Show/hide message information|<kbd>I</kbd>|
|Show/hide message sender information|<kbd>U</kbd>|
|Show/hide edit history (from message information)|<kbd>E</kbd>|
|View current message in browser (from message information)|<kbd>V</kbd>|
|Show/hide full rendered message (from message information)|<kbd>F</kbd>|
|Show/hide full raw message (from message information)|<kbd>R</kbd>|

## Stream list actions
|Command|Key Combination|
| :--- | :---: |
|Toggle topics in a stream|<kbd>t</kbd>|
|Mute/unmute Streams|<kbd>m</kbd>|
|Show/hide stream information & modify settings|<kbd>i</kbd>|
|Show/hide stream members (from stream information)|<kbd>m</kbd>|
|Copy stream email to clipboard (from stream information)|<kbd>c</kbd>|
|Toggle topics in a stream|<kbd>T</kbd>|
|Mute/unmute Streams|<kbd>M</kbd>|
|Show/hide stream information & modify settings|<kbd>I</kbd>|
|Show/hide stream members (from stream information)|<kbd>M</kbd>|
|Copy stream email to clipboard (from stream information)|<kbd>C</kbd>|

## Composing a Message
|Command|Key Combination|
| :--- | :---: |
|Cycle through recipient and content boxes|<kbd>tab</kbd>|
|Send a message|<kbd>ctrl</kbd> + <kbd>d</kbd> / <kbd>meta</kbd> + <kbd>enter</kbd>|
|Save current message as a draft|<kbd>meta</kbd> + <kbd>s</kbd>|
|Autocomplete @mentions, #stream_names, :emoji: and topics|<kbd>ctrl</kbd> + <kbd>f</kbd>|
|Cycle through autocomplete suggestions in reverse|<kbd>ctrl</kbd> + <kbd>r</kbd>|
|Narrow to compose box message recipient|<kbd>meta</kbd> + <kbd>.</kbd>|
|Jump to the beginning of line|<kbd>ctrl</kbd> + <kbd>a</kbd>|
|Jump to the end of line|<kbd>ctrl</kbd> + <kbd>e</kbd>|
|Jump backward one word|<kbd>meta</kbd> + <kbd>b</kbd>|
|Jump forward one word|<kbd>meta</kbd> + <kbd>f</kbd>|
|Delete previous character (to left)|<kbd>ctrl</kbd> + <kbd>h</kbd>|
|Transpose characters|<kbd>ctrl</kbd> + <kbd>t</kbd>|
|Cut forwards to the end of the line|<kbd>ctrl</kbd> + <kbd>k</kbd>|
|Cut backwards to the start of the line|<kbd>ctrl</kbd> + <kbd>u</kbd>|
|Cut forwards to the end of the current word|<kbd>meta</kbd> + <kbd>d</kbd>|
|Cut backwards to the start of the current word|<kbd>ctrl</kbd> + <kbd>w</kbd>|
|Paste last cut section|<kbd>ctrl</kbd> + <kbd>y</kbd>|
|Undo last action|<kbd>ctrl</kbd> + <kbd>_</kbd>|
|Jump to the previous line|<kbd>up</kbd> / <kbd>ctrl</kbd> + <kbd>p</kbd>|
|Jump to the next line|<kbd>down</kbd> / <kbd>ctrl</kbd> + <kbd>n</kbd>|
|Clear compose box|<kbd>ctrl</kbd> + <kbd>l</kbd>|
|Cycle through recipient and content boxes|<kbd>Tab</kbd>|
|Send a message|<kbd>Ctrl</kbd> + <kbd>D</kbd> / <kbd>Meta</kbd> + <kbd>Enter</kbd>|
|Save current message as a draft|<kbd>Meta</kbd> + <kbd>S</kbd>|
|Autocomplete @mentions, #stream_names, :emoji: and topics|<kbd>Ctrl</kbd> + <kbd>F</kbd>|
|Cycle through autocomplete suggestions in reverse|<kbd>Ctrl</kbd> + <kbd>R</kbd>|
|Narrow to compose box message recipient|<kbd>Meta</kbd> + <kbd>.</kbd>|
|Jump to the beginning of line|<kbd>Ctrl</kbd> + <kbd>A</kbd>|
|Jump to the end of line|<kbd>Ctrl</kbd> + <kbd>E</kbd>|
|Jump backward one word|<kbd>Meta</kbd> + <kbd>B</kbd>|
|Jump forward one word|<kbd>Meta</kbd> + <kbd>F</kbd>|
|Delete previous character (to left)|<kbd>Ctrl</kbd> + <kbd>H</kbd>|
|Transpose characters|<kbd>Ctrl</kbd> + <kbd>T</kbd>|
|Cut forwards to the end of the line|<kbd>Ctrl</kbd> + <kbd>K</kbd>|
|Cut backwards to the start of the line|<kbd>Ctrl</kbd> + <kbd>U</kbd>|
|Cut forwards to the end of the current word|<kbd>Meta</kbd> + <kbd>D</kbd>|
|Cut backwards to the start of the current word|<kbd>Ctrl</kbd> + <kbd>W</kbd>|
|Paste last cut section|<kbd>Ctrl</kbd> + <kbd>Y</kbd>|
|Undo last action|<kbd>Ctrl</kbd> + <kbd>_</kbd>|
|Jump to the previous line|<kbd>Up ↑</kbd> / <kbd>Ctrl</kbd> + <kbd>P</kbd>|
|Jump to the next line|<kbd>Down ↓</kbd> / <kbd>Ctrl</kbd> + <kbd>N</kbd>|
|Clear compose box|<kbd>Ctrl</kbd> + <kbd>L</kbd>|

2 changes: 1 addition & 1 deletion tests/config/test_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ def test_display_key_for_urwid_key(urwid_key: str, display_key: str) -> None:


COMMAND_TO_DISPLAY_KEYS = [
("NEXT_LINE", ["Down", "Ctrl n"]),
("NEXT_LINE", [f"Down {keys.DIRECTION_TO_SYMBOL_MAP['Down']}", "Ctrl n"]),
("TOGGLE_STAR_STATUS", ["Ctrl s", "*"]),
("ALL_PM", ["P"]),
]
Expand Down
44 changes: 39 additions & 5 deletions tools/lint-hotkeys
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ from collections import defaultdict
from pathlib import Path, PurePath
from typing import Dict, List, Tuple

from zulipterminal.config.keys import HELP_CATEGORIES, KEY_BINDINGS
from zulipterminal.config.keys import (
HELP_CATEGORIES,
KEY_BINDINGS,
display_keys_for_command,
)
from zulipterminal.config.symbols import ARROW_SYMBOLS


KEYS_FILE = (
Expand Down Expand Up @@ -69,7 +74,9 @@ def lint_hotkeys_file() -> None:
else:
print("No hotkeys linting errors")
if not output_file_matches_string(hotkeys_file_string):
print(f"Run './tools/{SCRIPT_NAME}' to update {OUTPUT_FILE_NAME} file")
print(
f"Run './tools/{SCRIPT_NAME} --fix' to update {OUTPUT_FILE_NAME} file"
)
error_flag = 1
sys.exit(error_flag)

Expand All @@ -85,6 +92,27 @@ def generate_hotkeys_file() -> None:
print(f"Hot Keys list saved in {OUTPUT_FILE}")


def format_key(key: str) -> str:
"""
Format each key of a hotkey combination for display
"""
if len(key) == 1:
if key.isupper():
return f"<kbd>Shift</kbd> + <kbd>{key}</kbd>"
return f"<kbd>{key.capitalize()}</kbd>"
return f"<kbd>{key}</kbd>"


def format_key_combination(key_combination: str) -> str:
"""
Format a hotkey combination for display
"""
for symbol in ARROW_SYMBOLS:
if symbol in key_combination:
return f"<kbd>{key_combination}</kbd>"
return " + ".join([format_key(key) for key in key_combination.split()])
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just added for an alternative option.
Also, there's the limitation that format_key_combination() does not work when the arrow key is used with modifier keys.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re the point about the arrow keys, again let's keep that for building later.

One issue here is how we're defining what a 'key' is for various purposes, ie. surrounding by <kbd> tags as a keyboard-key, or as a key-combination. Currently the display function just gives a string, but we're not limited to that data format until we ultimately must output it. Also, if we style the keys (physical keyboard and combinations) in the UI, we'll likely want to think of how we structure them in a similar way.



def get_hotkeys_file_string() -> str:
"""
Construct string in form for output to OUTPUT_FILE based on help text
Expand All @@ -104,7 +132,7 @@ def get_hotkeys_file_string() -> str:
for help_text, key_combinations_list in categories[action]:
various_key_combinations = " / ".join(
[
" + ".join([f"<kbd>{key}</kbd>" for key in key_combination.split()])
format_key_combination(key_combination)
for key_combination in key_combinations_list
]
)
Expand All @@ -114,6 +142,10 @@ def get_hotkeys_file_string() -> str:


def output_file_matches_string(hotkeys_file_string: str) -> bool:
"""
Read the existing OUTPUT_FILE into a string,
and compare if it matches the input string
"""
with open(OUTPUT_FILE) as output_file:
content_is_identical = hotkeys_file_string == output_file.read()
if content_is_identical:
Expand All @@ -129,8 +161,10 @@ def read_help_categories() -> Dict[str, List[Tuple[str, List[str]]]]:
Get all help categories from KEYS_FILE
"""
categories = defaultdict(list)
for item in KEY_BINDINGS.values():
categories[item["key_category"]].append((item["help_text"], item["keys"]))
for cmd, item in KEY_BINDINGS.items():
categories[item["key_category"]].append(
(item["help_text"], display_keys_for_command(cmd))
)
return categories


Expand Down
13 changes: 13 additions & 0 deletions zulipterminal/config/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
command_map,
)

from zulipterminal.config.symbols import DOWN_ARROW, LEFT_ARROW, RIGHT_ARROW, UP_ARROW


class KeyBinding(TypedDict):
keys: List[str]
Expand Down Expand Up @@ -466,6 +468,14 @@ def primary_key_for_command(command: str) -> str:
}


DIRECTION_TO_SYMBOL_MAP = {
"Up": UP_ARROW,
"Down": DOWN_ARROW,
"Left": LEFT_ARROW,
"Right": RIGHT_ARROW,
}


def display_key_for_urwid_key(urwid_key: str) -> str:
"""
Returns a displayable user-centric format of the urwid key.
Expand All @@ -479,6 +489,9 @@ def display_key_for_urwid_key(urwid_key: str) -> str:
else keyboard_key
for keyboard_key in urwid_key.split()
]
for direction, symbol in DIRECTION_TO_SYMBOL_MAP.items():
if direction in display_key:
display_key.append(symbol)
return " ".join(display_key)


Expand Down
6 changes: 6 additions & 0 deletions zulipterminal/config/symbols.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,9 @@
bline=_MESSAGE_RECIPIENTS_BOTTOM,
brcorner=_MESSAGE_RECIPIENTS_BOTTOM,
)

LEFT_ARROW = "←" # LEFTWARDS ARROW, U+2190
UP_ARROW = "↑" # UPWARDS ARROW, U+2191
RIGHT_ARROW = "→" # RIGHTWARDS ARROW, U+2192
DOWN_ARROW = "↓" # DOWNWARDS ARROW, U+2193
Comment on lines +90 to +93
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm inclined to keep this change as something that we choose to do universally, rather than only in the hotkeys doc.

We'll definitely want a space between the text and symbols if we do this.

Copy link
Collaborator Author

@Niloth-p Niloth-p Apr 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm inclined to keep this change as something that we choose to do universally, rather than only in the hotkeys doc.

We'll definitely want a space between the text and symbols if we do this.

Just to clarify,
We're updating all direction keys (and just the direction keys) to be - text + space + symbol.
Direction keys that are used in keys.py, in hotkeys.md and the ones in core.py (popup headers).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My main point was that we'll likely want to leave the symbol change to a different PR since I'm not sure we want it only in the docs. Specific symbols can be a separate discussion too - I recall some from the original PR weren't that clear.

Then, with the original changes you made I noticed that there wasn't a space, so for that future work the second comment I made was to indicate that we'll want a space if/when we do that.

ARROW_SYMBOLS = [LEFT_ARROW, UP_ARROW, RIGHT_ARROW, DOWN_ARROW]
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For convenient use in lint-hotkeys to check if the key_combination contains any of the arrow keys.

28 changes: 20 additions & 8 deletions zulipterminal/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from typing_extensions import Literal

from zulipterminal.api_types import Composition, Message
from zulipterminal.config.keys import primary_display_key_for_command
from zulipterminal.config.symbols import POPUP_CONTENT_BORDER, POPUP_TOP_LINE
from zulipterminal.config.themes import ThemeSpec
from zulipterminal.config.ui_sizes import (
Expand Down Expand Up @@ -51,6 +52,14 @@

ExceptionInfo = Tuple[Type[BaseException], BaseException, TracebackType]

SCROLL_PROMPT = (
"("
+ primary_display_key_for_command("GO_UP")
+ " / "
+ primary_display_key_for_command("GO_DOWN")
+ " scrolls)"
)


class Controller:
"""
Expand Down Expand Up @@ -246,11 +255,11 @@ def exit_popup(self) -> None:
self.loop.widget = self.view

def show_help(self) -> None:
help_view = HelpView(self, "Help Menu (up/down scrolls)")
help_view = HelpView(self, f"Help Menu {SCROLL_PROMPT}")
self.show_pop_up(help_view, "area:help")

def show_markdown_help(self) -> None:
markdown_view = MarkdownHelpView(self, "Markdown Help Menu (up/down scrolls)")
markdown_view = MarkdownHelpView(self, "Markdown Help Menu " + SCROLL_PROMPT)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Improvements and bugfixes to the doc/linting are fine in a related PR, but this commit looks like a different overall change - useful for consistency as it is 👍

You should be able to cherry-pick it into another branch just fine.

I've not tested this yet, but you updated the formatting of other lines but not this one?

This also prompts thoughts on ensuring we're consistent with naming of the menu titles and their names in the descriptions of the related hotkeys, which I've not checked recently, and I'm not sure if it's worth checking technically, or manually.

self.show_pop_up(markdown_view, "area:help")

def show_topic_edit_mode(self, button: Any) -> None:
Expand All @@ -266,7 +275,7 @@ def show_msg_info(
msg_info_view = MsgInfoView(
self,
msg,
"Message Information (up/down scrolls)",
f"Message Information {SCROLL_PROMPT}",
topic_links,
message_links,
time_mentions,
Expand Down Expand Up @@ -315,7 +324,10 @@ def show_about(self) -> None:
def show_user_info(self, user_id: int) -> None:
self.show_pop_up(
UserInfoView(
self, user_id, "User Information (up/down scrolls)", "USER_INFO"
self,
user_id,
f"User Information {SCROLL_PROMPT}",
"USER_INFO",
),
"area:user",
)
Expand All @@ -325,7 +337,7 @@ def show_msg_sender_info(self, user_id: int) -> None:
UserInfoView(
self,
user_id,
"Message Sender Information (up/down scrolls)",
f"Message Sender Information {SCROLL_PROMPT}",
"MSG_SENDER_INFO",
),
"area:user",
Expand All @@ -345,7 +357,7 @@ def show_full_rendered_message(
topic_links,
message_links,
time_mentions,
"Full rendered message (up/down scrolls)",
f"Full rendered message {SCROLL_PROMPT}",
),
"area:msg",
)
Expand All @@ -364,7 +376,7 @@ def show_full_raw_message(
topic_links,
message_links,
time_mentions,
"Full raw message (up/down scrolls)",
f"Full raw message {SCROLL_PROMPT}",
),
"area:msg",
)
Expand All @@ -383,7 +395,7 @@ def show_edit_history(
topic_links,
message_links,
time_mentions,
"Edit History (up/down scrolls)",
f"Edit History {SCROLL_PROMPT}",
),
"area:msg",
)
Expand Down