Zulip Terminal - Zulip's official terminal client
Recent changes | Configuration | Hot Keys | FAQs | Development | Tutorial
Zulip Terminal is the official terminal client for Zulip, providing a text-based user interface (TUI).
Specific aims include:
- Providing a broadly similar user experience to the Zulip web client, ultimately supporting all of its features
- Enabling all actions to be achieved through the keyboard (see Hot keys)
- Exploring alternative user interface designs suited to the display and input constraints
- Supporting a wide range of platforms and terminal emulators
- Making best use of available rows/columns to scale from 80x24 upwards (see Small terminal notes)
Learn how to use Zulip Terminal with our Tutorial.
We consider the client to already provide a fairly stable moderately-featureful everyday-user experience.
The terminal client currently has a number of intentional differences to the Zulip web client:
- Additional and occasionally different
Hot keys
to better support keyboard-only navigation; other than directional movement
these also include:
- z - zoom in/out, between streams & topics, or all direct messages & specific conversations
- t - toggle view of topics for a stream in left panel (later adopted for recent topics in web client)
- # - narrow to messages in which you're mentioned (@ is already used)
- f - narrow to messages you've starred (are following)
- Not marking additional messages read when the end of a conversation is visible (FAQ entry)
- Emoji and reactions are rendered as text only, for maximum terminal/font compatibility
- Footlinks - footnotes for links (URLs) - make messages readable, while retaining a list of links to cross-reference
- Content previewable in the web client, such as images, are also stored as footlinks
The current development focus is on improving aspects of everyday usage which are more commonly used - to reduce the need for users to temporarily switch to another client for a particular feature.
Current limitations which we expect to only resolve over the long term include support for:
- All operations performed by users with extra privileges (owners/admins)
- Accessing and updating all settings
- Using a mouse/pointer to achieve all actions
- An internationalized UI
For queries on missing feature support please take a look at the Frequently Asked Questions (FAQs), our open Issues, or sign up on https://chat.zulip.org and chat with users and developers in the #zulip-terminal stream!
- Linux
- OSX
- WSL (On Windows)
The minimum server version that Zulip Terminal supports is
2.1.0
.
It may still work with earlier versions.
Version 0.7.0 was the last release with support for Python 3.6.
Version 0.6.0 was the last release with support for Python 3.5.
Later releases and the main development branch are currently tested (on Ubuntu) with:
- CPython 3.7-3.11
- PyPy 3.7-3.9
Since our automated testing does not cover interactive testing of the UI, there may be issues with some Python versions, though generally we have not found this to be the case.
Please note that generally we limit each release to between a lower and upper
Python version, so it is possible that for example if you have a newer version
of Python installed, then some releases (or main
) may not install correctly.
In some cases this can give rise to the symptoms in issue #1145.
We recommend installing in a dedicated python virtual environment (see below) or using an automated option such as pipx
-
Stable releases - These are available on PyPI as the package zulip-term
To install, run a command like:
pip3 install zulip-term
-
Latest (git) versions - The latest development version can be installed from the git repository
main
branchTo install, run a command like:
pip3 install git+https://github.com/zulip/zulip-terminal.git@main
We also provide some sample Dockerfiles to build docker images in docker/.
With the python 3.6+ required for running, the following should work on most systems:
python3 -m venv zt_venv
(creates a virtual environment namedzt_venv
in the current directory)source zt_venv/bin/activate
(activates the virtual environment; this assumes a bash-like shell)- Run one of the install commands above,
If you open a different terminal window (or log-off/restart your computer),
you'll need to run step 2 of the above list again before running
zulip-term
, since that activates that virtual environment.
You can read more about virtual environments in the
Python 3 library venv documentation.
Note that there is no automatic-update system, so please track the update locations relevant to your installation version:
Stable releases
Before upgrading, we recommend you check the Changes in recent releases so you are aware of any important changes between releases.
-
These are now announced in the #announce>terminal releases topic on the Zulip Community server (https://chat.zulip.org), which is visible without an account.
If you wish to receive emails when updates are announced, you are welcome to sign up for an account on this server, which will enable you to enable email notifications for the #announce stream (help article, notifications settings on chat.zulip.org).
-
You can also customize your GitHub Watch setting on the project page to include releases.
-
PyPI provides a RSS Release feed, and various other services track this information.
Latest (git) versions
Versions installed from the main
git branch will also not update
automatically - the 'latest' refers to the status at the point of installation.
This also applies to other source or development installs (eg. https://aur.archlinux.org/packages/python-zulip-term-git/).
Therefore, upgrade your package using the command above, or one pertinent to your package system (eg. Arch).
While the main
branch is intended to remain stable, if upgrading between two
arbitrary 'latest' versions, please be aware that changes are not summarized,
though our commit log should be very readable.
Upon first running zulip-term
it looks for a zuliprc
file, by default in
your home directory, which contains the details to log into a Zulip server.
If it doesn't find this file, you have two options:
-
zulip-term
will prompt you for your server, email and password, and create azuliprc
file for you in that locationNOTE: If you use Google, Github or another external authentication to access your Zulip organization then you likely won't have a password set and currently need to create one to use zulip-terminal.
- If your organization is on Zulip cloud, you can visit https://zulip.com/accounts/go?next=/accounts/password/reset to create a new password for your account.
- For self-hosted servers please go to your equivalent of
<Zulip server URL>/accounts/password/reset/
to create a new password for your account (eg: https://chat.zulip.org/accounts/password/reset/).
-
Each time you run
zulip-term
, you can specify the path to an alternativezuliprc
file using the-c
or--config-file
options, eg.$ zulip-term -c /path/to/zuliprc
A
.zuliprc
file corresponding to your account on a particular Zulip server can be downloaded via Web or Desktop applications connected to that server. In recent versions this can be found in your Personal settings in the Account & privacy section, under API key as 'Show/change your API key'.If this is your only Zulip account, you may want to move and rename this file to the default file location above, or rename it to something more memorable that you can pass to the
---config-file
option. This.zuliprc
file gives you all the permissions you have as that user.Similar
.zuliprc files
can be downloaded from the Bots section for any bots you have set up, though with correspondingly limited permissions.
NOTE: If your server uses self-signed certificates or an insecure
connection, you will need to add extra options to the zuliprc
file manually -
see the documentation for the Zulip python module.
We suggest running zulip-term
using the -e
or --explore
option (in
explore mode) when you are trying Zulip Terminal for the first time, where we
intentionally do not mark messages as read. Try following along with our
Tutorial
to get the hang of things.
The zuliprc
file contains two sections:
- an
[api]
section with information required to connect to your Zulip server - a
[zterm]
section with configuration specific tozulip-term
A file with only the first section can be auto-generated in some cases by
zulip-term
, or you can download one from your account on your server (see
above). Parts of the second section can be added and adjusted in stages when
you wish to customize the behavior of zulip-term
.
The example below, with dummy [api]
section contents, represents a working
configuration file with all the default compatible [zterm]
values uncommented
and with accompanying notes:
[api]
[email protected]
key=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
site=https://example.zulipchat.com
[zterm]
## Theme: available themes can be found by running `zulip-term --list-themes`, or in docs/FAQ.md
theme=zt_dark
## Autohide: set to 'autohide' to hide the left & right panels except when they're focused
autohide=no_autohide
## Footlinks: set to 'disabled' to hide footlinks; 'enabled' will show the first 3 per message
## For more flexibility, comment-out this value, and un-comment maximum-footlinks below
footlinks=enabled
## Maximum-footlinks: set to any value 0 or greater, to limit footlinks shown per message
# maximum-footlinks=3
## Notify: set to 'enabled' to display notifications (see elsewhere for configuration notes)
notify=disabled
## Color-depth: set to one of 1 (for monochrome), 16, 256, or 24bit
color-depth=256
NOTE: Most of these configuration settings may be specified on the command line when
zulip-term
is started;zulip-term -h
orzulip-term --help
will give the full list of options.
Note that notifications are not currently supported on WSL; see #767.
The following command installs notify-send
on Debian based systems, similar
commands can be found for other linux systems as well.
sudo apt-get install libnotify-bin
No additional package is required to enable notifications in OS X.
However to have a notification sound, set the following variable (based on your
type of shell).
The sound value (here Ping) can be any one of the .aiff
files found at
/System/Library/Sounds
or ~/Library/Sounds
.
Bash
echo 'export ZT_NOTIFICATION_SOUND=Ping' >> ~/.bash_profile
source ~/.bash_profile
ZSH
echo 'export ZT_NOTIFICATION_SOUND=Ping' >> ~/.zshenv
source ~/.zshenv
Zulip Terminal allows users to copy certain texts to the clipboard via a Python
module, Pyperclip
.
This module makes use of various system packages which may or may not come with
the OS.
The "Copy to clipboard" feature is currently only available for copying Stream
email, from the Stream information popup.
On Linux, this module makes use of xclip
or xsel
commands, which should
already come with the OS.
If none of these commands are installed on your system, then install any ONE
using:
sudo apt-get install xclip [Recommended]
OR
sudo apt-get install xsel
No additional package is required to enable copying to clipboard.
Zulip Terminal is being built by the awesome Zulip community.
To be a part of it and to contribute to the code, feel free to work on any issue or propose your idea on #zulip-terminal.
For commit structure and style, please review the Commit Style section below.
If you are new to git
(or not!), you may benefit from the Zulip git guide.
When contributing, it's important to note that we use a rebase-oriented workflow.
A simple tutorial is available for implementing the typing
indicator.
Follow it to understand how to implement a new feature for zulip-terminal.
You can of course browse the source on GitHub & in the source tree you download, and check the source file overview for ideas of how files are currently arranged.
Zulip Terminal uses urwid to render the UI components in terminal. Urwid is an awesome library through which you can render a decent terminal UI just using python. Urwid's Tutorial is a great place to start for new contributors.
First, fork the zulip/zulip-terminal
repository on GitHub (see how) and then clone your forked repository locally, replacing YOUR_USERNAME with your GitHub username:
$ git clone --config pull.rebase [email protected]:YOUR_USERNAME/zulip-terminal.git
This should create a new directory for the repository in the current directory, so enter the repository directory with cd zulip-terminal
and configure and fetch the upstream remote repository for your cloned fork of Zulip Terminal:
$ git remote add -f upstream https://github.com/zulip/zulip-terminal.git
For detailed explanation on the commands used for cloning and setting upstream, refer to Step 1 of the Get Zulip Code section of Zulip's Git guide.
Various options are available; we are exploring the benefits of each and would appreciate feedback on which you use or feel works best.
Note that the tools used in each case are typically the same, but are called in different ways.
The following commands should be run in the repository directory, created by a process similar to that in the previous section.
- Install pipenv (see the recommended installation notes; pipenv can be installed in a virtual environment, if you wish)
$ pip3 install --user pipenv
- Initialize the pipenv virtual environment for zulip-term (using the default python 3; use eg.
--python 3.6
to be more specific)
$ pipenv --three
- Install zulip-term, with the development requirements
$ pipenv install --dev
$ pipenv run pip3 install -e '.[dev]'
-
Manually create & activate a virtual environment; any method should work, such as that used in the above simple installation
python3 -m venv zt_venv
(creates a venv namedzt_venv
in the current directory)source zt_venv/bin/activate
(activates the venv; this assumes a bash-like shell)
-
Install zulip-term, with the development requirements
$ pip3 install -e '.[dev]'
This is the newest and simplest approach, if you have make
installed:
make
(sets up an installed virtual environment inzt_venv
in the current directory)source zt_venv/bin/activate
(activates the venv; this assumes a bash-like shell)
Once you have a development environment set up, you might find the following useful, depending upon your type of environment:
Task | Make & Pip | Pipenv |
---|---|---|
Run normally | zulip-term |
pipenv run zulip-term |
Run in debug mode | zulip-term -d |
pipenv run zulip-term -d |
Run with profiling | zulip-term --profile |
pipenv run zulip-term --profile |
Run all linters | ./tools/lint-all |
pipenv run ./tools/lint-all |
Run all tests | pytest |
pipenv run pytest |
Build test coverage report | pytest --cov-report html:cov_html --cov=./ |
pipenv run pytest --cov-report html:cov_html --cov=./ |
If using make with pip, running make
will ensure the development environment is up to date with the specified dependencies, useful after fetching from git and rebasing.
The linters and automated tests (pytest) are run in CI (GitHub Actions) when you submit a pull request (PR), and we expect them to pass before code is merged.
NOTE: Mergeable PRs with multiple commits are expected to pass linting and tests at each commit, not simply overall
Running these tools locally can speed your development and avoid the need to repeatedly push your code to GitHub simply to run these checks.
If you have troubles understanding why the linters or pytest are failing, please do push your code to a branch/PR and we can discuss the problems in the PR or on chat.zulip.org.
All linters and tests can be run using the commands in the table above.
Individual linters may also be run via scripts in tools/
.
In addition, if using a make
-based system:
make lint
andmake test
run all of each group of tasksmake check
runs all checks, which is useful before pushing a PR (or an update)
Correcting some linting errors requires manual intervention, such as from
mypy
for type-checking.
However, other linting errors may be fixed automatically, as detailed below -
this can save a lot of time manually adjusting your code to pass the
linters!
If you update these, note that you do not need to update the text in both places manually to pass linting.
The source of truth is in the source code, so simply update the python file and run the relevant tool, as detailed below.
Currently we have
tools/lint-hotkeys --fix
to regenerate docs/hotkeys.md from config/keys.pytools/lint-docstring --fix
to regenerate docs/developer-file-overview.md from file docstrings
(these tools are also used for the linting process to ensure that these files are synchronzed)
The project uses black
and isort
for code-style and import sorting respectively.
These tools can be run as linters locally , but can also automatically format your code for you.
If you're using a make
-based setup, running make fix
will run both (and a
few other tools) and reformat the current state of your code - so you'll want
to commit first just in case, then --amend
that commit if you're happy with
the changes.
You can also use the tools individually on a file or directory, eg.
black zulipterminal
or isort tests/model/test_model.py
As you work locally, investigating changes to make, it's common to make a series of small commits to store your progress. Often this can include commits that fix linting or testing issues in the previous commit(s). These are developmental-style commits - and almost everyone is likely to write commits in this style to some degree.
Developmental-style commits store the changes just fine for you right now. However, when sharing your code, commit messages are a great place to instead communicate to others what you're changing and why. Tidying the structure can also make it easier and quicker for a reader to understand changes, and that you respect their time. One example is that very large single commits can take a lot of time to review, compared to if they are split up. Another is if you fix the tests/linting in a commit: which commit (or commits!) does this fix, and if it's in the same branch/PR, why isn't the original commit just fixed instead?
Therefore, when creating a Pull Request (PR), please consider that your code is more likely to be merged, more quickly, if it is easier to read, understand and review - and a big part of that is how you structure your changes into commits, and describe those changes in commit messages.
To be productive and make it easier for your PRs to be reviewed and updated, we follow an approach taken at Zulip and elsewhere, aiming for PRs to consist of a series of minimal coherent commits:
- Minimal: Look at each commit. Does it make different kinds of changes to lots of files? Are you treating the title of a commit as a list of changes? Consider how to break down a large change into a sequence of smaller changes that you can individually easily describe and that can flow after one another.
- Coherent: Each commit should individually pass all linting and existing automated tests, and if you add new behavior, then add or extend the tests to cover it.
Note that keeping to these principles can give other benefits, both before, during and after reviewing a PR, including:
- Minimal commits can always later be squashed (combined) together - splitting commits is more challenging!
- Coherent commits mean nothing should be broken between and after commits
- If a new commit in your branch breaks the linting/tests, it's likely that commit at fault!
- This improves the utility of
git bisect
in your branch or onmain
- These commits are easier to reorder in a branch
- Commits at the start of a branch (or can be moved there), may be merged early to simplify a branch
- Understanding what changes were made and why is easier when reading a git log comprising these commits
We now enforce a limited aspect of the coherent nature of commits in a PR in
a job as part of our Continuous Integration (CI), Ensure isolated PR commits,
which essentially runs make check
on each commit in your branch. You can
replicate this locally before pushing to GitHub using tools/check-branch
.
While small or proof-of-concept PRs are initially fine to push as they are, they will likely only be reviewed based on the overall changes. Generally if individual commits look like they have a developmental style then reviewers are likely to give less specific feedback, and minimal coherent commits are certain to be requested before merging.
Restructuring commands - Most restructuring relies upon interactive
rebasing (eg. git rebase -i upstream/main
), but consider searching online
for specific actions, as well as searching or asking in #git help or
#learning on chat.zulip.org.
Self review - Another useful approach is to review your own commits both locally (see Zulip suggestions) and after you push to GitHub. This allows you to inspect and fix anything that looks out of place, which someone is likely to pick up in their review, helping your submissions look more polished, as well as again indicating that you respect reviewers' time.
We aim to follow a standard commit style to keep the git log
consistent and easy to read.
Much like working with code, we suggest you refer to recent commits in the git log, for examples of the style we're actively using.
Our overall style for commit messages broadly follows the general guidelines given for Zulip Commit messages, so we recommend reading that first.
Our commit titles (summaries) have slight variations from the general Zulip style, with each:
- starting with one or more areas - in lower case, followed by a colon and space
- generally each commit has at least one area of each modified file - without extensions, separated by a
/
- generally don't include test files in this list (instead
Tests updated
orTests added
in the commit text) - other common areas include types like
refactor:
,bugfix:
andrequirements:
(see below)
- generally each commit has at least one area of each modified file - without extensions, separated by a
- ending with a concise description starting with a capital and ending with a full-stop (period)
- having a maximum overall length of 72 (fitting the github web interface without abbreviation)
Some example commit titles: (ideally more descriptive in practice!)
file3/file1/file2: Improve behavior of something.
- a general commit updating files
file1.txt
,file2.py
andfile3.md
- a general commit updating files
refactor: file1/file2: Extract some common function.
- a pure refactor which doesn't change the functional behavior, involving
file1.py
andfile2.py
- a pure refactor which doesn't change the functional behavior, involving
bugfix: file1: Avoid some noticeable bug.
- a small commit to fix a bug in
file1.py
- a small commit to fix a bug in
tests: file1: Improve test for something.
- only improve tests for
file1
, likely intest_file1.py
- only improve tests for
requirements: Upgrade some-dependency from ==9.2 to ==9.3.
- upgrade a dependency from version ==9.2 to version ==9.3, in the central dependencies file (not some file requirements.*)
To aid in satisfying some of these rules you can use GitLint
, as described in the following section.
However, please check your commits manually versus these style rules, since GitLint cannot check everything - including language or grammar!
If you plan to submit git commits in pull-requests (PRs), then we highly suggest installing the gitlint
commit-message hook by running gitlint install-hook
(or pipenv run gitlint install-hook
with pipenv setups). While the content still depends upon your writing skills, this ensures a more consistent formatting structure between commits, including by different authors.
If the hook is installed as described above, then after completing the text for a commit, it will be checked by gitlint against the style we have set up, and will offer advice if there are any issues it notices. If gitlint finds any, it will ask if you wish to commit with the message as it is (y
for 'yes'), stop the commit process (n
for 'no'), or edit the commit message (e
for 'edit').
Other gitlint options are available; for example it is possible to apply it to a range of commits with the --commits
option, eg. gitlint --commits HEAD~2..HEAD
would apply it to the last few commits.
Tests for zulip-terminal are written using pytest. You can read the tests in the /tests
folder to learn about writing tests for a new class/function. If you are new to pytest, reading its documentation is definitely recommended.
We currently have thousands of tests which get checked upon running pytest
. While it is dependent on your system capability, this should typically take less than one minute to run. However, during debugging you may still wish to limit the scope of your tests, to improve the turnaround time:
- If lots of tests are failing in a very verbose way, you might try the
-x
option (eg.pytest -x
) to stop tests after the first failure; due to parametrization of tests and test fixtures, many apparent errors/failures can be resolved with just one fix! (try eg.pytest --maxfail 3
for a less-strict version of this) - To avoid running all the successful tests each time, along with the failures, you can run with
--lf
(eg.pytest --lf
), short for--last-failed
(similar useful options may be--failed-first
and--new-first
, which may work well with-x
) - Since pytest 3.10 there is
--sw
(--stepwise
), which works through known failures in the same way as--lf
and-x
can be used, which can be combined with--stepwise-skip
to control which test is the current focus - If you know the names of tests which are failing and/or in a specific location, you might limit tests to a particular location (eg.
pytest tests/model
) or use a selected keyword (eg.pytest -k __handle
)
When only a subset of tests are running it becomes more practical and useful to use the -v
option (--verbose
); instead of showing a .
(or F
, E
, x
, etc) for each test result, it gives the name (with parameters) of each test being run (eg. pytest -v -k __handle
). This option also shows more detail in tests and can be given multiple times (eg. pytest -vv
).
For additional help with pytest options see pytest -h
, or check out the full pytest documentation.
The stdout (standard output) for zulip-terminal is redirected to ./debug.log
if debugging is enabled at run-time using -d
or --debug
.
This means that if you want to check the value of a variable, or perhaps indicate reaching a certain point in the code, you can simply use print()
, eg.
print(f"Just about to do something with {variable}")
and when running with a debugging option, the string will be printed to ./debug.log
.
With a bash-like terminal, you can run something like tail -f debug.log
in another terminal, to see the output from print
as it happens.
If you want to debug zulip-terminal while it is running, or in a specific state, you can insert
from pudb.remote import set_trace
set_trace()
in the part of the code you want to debug. This will start a telnet connection for you. You can find the IP address and
port of the telnet connection in ./debug.log
. Then simply run
$ telnet 127.0.0.1 6899
in another terminal, where 127.0.0.1
is the IP address and 6899
is port you find in ./debug.log
.
This likely means that you have installed both normal and development versions of zulip-terminal.
To ensure you run the development version:
- If using pipenv, call
pipenv run zulip-term
from the cloned/downloadedzulip-terminal
directory; - If using pip (pip3), ensure you have activated the correct virtual environment (venv); depending on how your shell is configured, the name of the venv may appear in the command prompt. Note that not including the
-e
in the pip3 command will also cause this problem.