kaustubh-nair: This is an alternative to #725
zulipbot: Heads up @kaustubh-nair, we just merged some commits that conflict with the changes your made in this pull request! You can review this repository's [recent commits](https://github.com/zulip/zulip-terminal/commits/main) to see where the conflicts occur. Please rebase your feature branch against the `upstream/main` branch and [resolve](https://zulip.readthedocs.io/en/latest/git/troubleshooting.html#recover-from-a-git-rebase-failure) your pull request's merge conflicts accordingly.

<!-- mergeConflictWarning -->

neiljp: @kaustubh-nair Rebased and merged this great simple fix with minor tweaks, thanks! :tada:
diff --git a/zulipterminal/ui.py b/zulipterminal/ui.py
index d998569..544f9d7 100644
--- a/zulipterminal/ui.py
+++ b/zulipterminal/ui.py
@@ -103,6 +103,11 @@ class View(urwid.WidgetWrap):
         style: str = "footer",
         duration: Optional[float] = None,
     ) -> None:
+        # Avoid updating repeatedly (then pausing and showing default text)
+        # This is simple, though doesn't avoid starting one thread for each call
+        if text_list == self._w.footer.text:
+            return
+
         if text_list is None:
             text = self.get_random_help()
         else:

Footer gets updated as many times as the edit key is pressed on any uneditable message
Steps to reproduce:
* Move focus to any message that you can't edit
* Press e multiple times
* Press any other key to trigger the behaviour
diff --git a/tests/ui/test_ui.py b/tests/ui/test_ui.py
index 8b2340e..7e50281 100644
--- a/tests/ui/test_ui.py
+++ b/tests/ui/test_ui.py
@@ -76,6 +76,15 @@ class TestView:
         assert view.users_view == right_view()
         assert return_value == (line_box(), right_tab())
 
+    def test_set_footer_text_same_test(
+        self, view: View, mocker: MockerFixture, text: List[str] = ["heya"]
+    ) -> None:
+        view._w.footer.text = text
+
+        view.set_footer_text(text)
+
+        view._w.footer.set_text.assert_not_called()
+
     def test_set_footer_text_default(self, view: View, mocker: MockerFixture) -> None:
         mocker.patch(VIEW + ".get_random_help", return_value=["some help text"])
 

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-mock
apt-get update apt-get install -y gcc
3.9
requirements/base.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
arrow==1.2.3
attrs==25.3.0
autoflake==1.3.1
autopep8==1.5.7
beautifulsoup4==4.13.3
black==25.1.0
certifi==2025.1.31
charset-normalizer==3.4.1
click==8.1.3
coverage==7.8.0
distro==1.9.0
exceptiongroup==1.2.2
flake8==3.9.2
flake8-continuation==1.0.5
flake8-quotes==3.2.0
gitlint==0.19.1
gitlint-core==0.19.1
idna==3.10
iniconfig==2.1.0
isort==5.7.0
lxml==5.3.1
mccabe==0.6.1
mypy==0.910
mypy_extensions==0.4.4
packaging==24.2
pathspec==0.12.1
platformdirs==4.3.7
pluggy==1.5.0
pudb==2017.1.4
py==1.11.0
pycodestyle==2.7.0
pyflakes==2.3.1
Pygments==2.19.1
pyperclip==1.9.0
pytest==6.2.5
pytest-cov==2.11.1
pytest-mock==3.6.1
python-dateutil==2.9.0.post0
pytz==2025.2
requests==2.32.3
sh==1.14.3
six==1.17.0
snakeviz==0.4.2
soupsieve==2.6
toml==0.10.2
tomli==2.2.1
tornado==6.4.2
types-python-dateutil==2.9.0.20241206
types-pytz==2025.2.0.20250326
types-requests==2.32.0.20250328
types-tzlocal==5.1.0.1
typing_extensions==4.13.0
tzlocal==5.3.1
urllib3==2.3.0
urwid==2.1.2
urwid_readline==0.15.1
zulip==0.9.0
-e git+https://github.com/zulip/zulip-terminal.git@80bb9c0d75f56db98b06da96af411425c75a7304#egg=zulip_term

name: zulip-terminal
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - arrow==1.2.3
      - attrs==25.3.0
      - autoflake==1.3.1
      - autopep8==1.5.7
      - beautifulsoup4==4.13.3
      - black==25.1.0
      - certifi==2025.1.31
      - charset-normalizer==3.4.1
      - click==8.1.3
      - coverage==7.8.0
      - distro==1.9.0
      - exceptiongroup==1.2.2
      - flake8==3.9.2
      - flake8-continuation==1.0.5
      - flake8-quotes==3.2.0
      - gitlint==0.19.1
      - gitlint-core==0.19.1
      - idna==3.10
      - iniconfig==2.1.0
      - isort==5.7.0
      - lxml==5.3.1
      - mccabe==0.6.1
      - mypy==0.910
      - mypy-extensions==0.4.4
      - packaging==24.2
      - pathspec==0.12.1
      - platformdirs==4.3.7
      - pluggy==1.5.0
      - pudb==2017.1.4
      - py==1.11.0
      - pycodestyle==2.7.0
      - pyflakes==2.3.1
      - pygments==2.19.1
      - pyperclip==1.9.0
      - pytest==6.2.5
      - pytest-cov==2.11.1
      - pytest-mock==3.6.1
      - python-dateutil==2.9.0.post0
      - pytz==2025.2
      - requests==2.32.3
      - sh==1.14.3
      - six==1.17.0
      - snakeviz==0.4.2
      - soupsieve==2.6
      - toml==0.10.2
      - tomli==2.2.1
      - tornado==6.4.2
      - types-python-dateutil==2.9.0.20241206
      - types-pytz==2025.2.0.20250326
      - types-requests==2.32.0.20250328
      - types-tzlocal==5.1.0.1
      - typing-extensions==4.13.0
      - tzlocal==5.3.1
      - urllib3==2.3.0
      - urwid==2.1.2
      - urwid-readline==0.15.1
      - zulip==0.9.0
      - zulip-term==0.6.0+git
prefix: /opt/conda/envs/zulip-terminal

tests/ui/test_ui.py::TestView::test_set_footer_text_same_test
tests/ui/test_ui.py::TestView::test_init tests/ui/test_ui.py::TestView::test_left_column_view tests/ui/test_ui.py::TestView::test_middle_column_view tests/ui/test_ui.py::TestView::test_right_column_view tests/ui/test_ui.py::TestView::test_set_footer_text_default tests/ui/test_ui.py::TestView::test_set_footer_text_specific_text tests/ui/test_ui.py::TestView::test_set_footer_text_with_duration tests/ui/test_ui.py::TestView::test_set_typeahead_footer[no_matches] tests/ui/test_ui.py::TestView::test_set_typeahead_footer[no_highlight] tests/ui/test_ui.py::TestView::test_set_typeahead_footer[no_highlight_truncated] tests/ui/test_ui.py::TestView::test_set_typeahead_footer[first_suggestion_highlighted] tests/ui/test_ui.py::TestView::test_set_typeahead_footer[first_suggestion_highlighted_truncated] tests/ui/test_ui.py::TestView::test_set_typeahead_footer[last_suggestion_highlighted] tests/ui/test_ui.py::TestView::test_footer_view tests/ui/test_ui.py::TestView::test_main_window tests/ui/test_ui.py::TestView::test_show_panel_methods[left_panel-True-True] tests/ui/test_ui.py::TestView::test_show_panel_methods[left_panel-True-False] tests/ui/test_ui.py::TestView::test_show_panel_methods[left_panel-False-True] tests/ui/test_ui.py::TestView::test_show_panel_methods[left_panel-False-False] tests/ui/test_ui.py::TestView::test_show_panel_methods[right_panel-True-True] tests/ui/test_ui.py::TestView::test_show_panel_methods[right_panel-True-False] tests/ui/test_ui.py::TestView::test_show_panel_methods[right_panel-False-True] tests/ui/test_ui.py::TestView::test_show_panel_methods[right_panel-False-False] tests/ui/test_ui.py::TestView::test_keypress_normal_mode_navigation[nav-key:u] tests/ui/test_ui.py::TestView::test_keypress_normal_mode_navigation[nav-key:k] tests/ui/test_ui.py::TestView::test_keypress_normal_mode_navigation[nav-key:d] tests/ui/test_ui.py::TestView::test_keypress_normal_mode_navigation[nav-key:j] tests/ui/test_ui.py::TestView::test_keypress_normal_mode_navigation[nav-key:l0] tests/ui/test_ui.py::TestView::test_keypress_normal_mode_navigation[nav-key:h] tests/ui/test_ui.py::TestView::test_keypress_normal_mode_navigation[nav-key:r] tests/ui/test_ui.py::TestView::test_keypress_normal_mode_navigation[nav-key:l1] tests/ui/test_ui.py::TestView::test_keypress_normal_mode_navigation[nav-key:p0] tests/ui/test_ui.py::TestView::test_keypress_normal_mode_navigation[nav-key:K] tests/ui/test_ui.py::TestView::test_keypress_normal_mode_navigation[nav-key:p1] tests/ui/test_ui.py::TestView::test_keypress_normal_mode_navigation[nav-key:J] tests/ui/test_ui.py::TestView::test_keypress_normal_mode_navigation[nav-key:e] tests/ui/test_ui.py::TestView::test_keypress_normal_mode_navigation[nav-key:G] tests/ui/test_ui.py::TestView::test_keypress_ALL_MENTIONS[#] tests/ui/test_ui.py::TestView::test_keypress_STREAM_MESSAGE[autohide-c] tests/ui/test_ui.py::TestView::test_keypress_STREAM_MESSAGE[no_autohide-c] tests/ui/test_ui.py::TestView::test_keypress_autohide_users[autohide-w] tests/ui/test_ui.py::TestView::test_keypress_autohide_users[no_autohide-w] tests/ui/test_ui.py::TestView::test_keypress_autohide_streams[autohide-q] tests/ui/test_ui.py::TestView::test_keypress_autohide_streams[no_autohide-q] tests/ui/test_ui.py::TestView::test_keypress_OPEN_DRAFT[autohide-d-stream_draft_composition] tests/ui/test_ui.py::TestView::test_keypress_OPEN_DRAFT[autohide-d-private_draft_composition] tests/ui/test_ui.py::TestView::test_keypress_OPEN_DRAFT[autohide-d-no_draft_composition] tests/ui/test_ui.py::TestView::test_keypress_OPEN_DRAFT[no_autohide-d-stream_draft_composition] tests/ui/test_ui.py::TestView::test_keypress_OPEN_DRAFT[no_autohide-d-private_draft_composition] tests/ui/test_ui.py::TestView::test_keypress_OPEN_DRAFT[no_autohide-d-no_draft_composition] tests/ui/test_ui.py::TestView::test_keypress_edit_mode[w]
zulipterminal/ui.py
zulipterminal/ui.py