sqlite-utils icon indicating copy to clipboard operation
sqlite-utils copied to clipboard

CLI eats my cursor

Open chapmanjacobd opened this issue 2 years ago • 3 comments

I'm not sure why this happens but sqlite-utils makes my terminal cursor disappear after running commands like sqlite-utils insert. I've only noticed this behavior in sqlite-utils. Not sure if it is a bug in kitty, fish or sqlite-utils

I can still type commands after it runs but the text cursor is invisible

chapmanjacobd avatar May 17 '22 18:05 chapmanjacobd

I can confirm. This only happens with sqlite-utils. I am using gnome-terminal with bash.

frafra avatar May 27 '22 10:05 frafra

Marking this as help wanted because I can't figure out how to replicate it!

simonw avatar Jun 14 '22 21:06 simonw

When I run reset it fixes my terminal. I suspect it is related to the progress bar

https://linux.die.net/man/1/reset

950 1s /m/d/03_Downloads 🐑 echo $TERM
xterm-kitty
▓░▒░ /m/d/03_Downloads 🌏 kitty -v
kitty 0.26.2 created by Kovid Goyal
$ sqlite-utils insert test.db facility facility-boundary-us-all.csv --csv
blah blah blah (no offense)
$ <no cursor>
$ reset
$ <cursor lives again (resurrection [explicit])>

chapmanjacobd avatar Sep 20 '22 20:09 chapmanjacobd

Came here to say that I also have this issue.

alecstein avatar Feb 03 '23 22:02 alecstein

I think I see what is happening here, although I haven't quite work out a fix yet. Usually:

  • click.progressbar.render_progress() renders the cursor invisible on each invocation (update of the bar)
  • When the progress bar goes out of scope, the __exit()__ method is invoked, which calls render_finish() to make the cursor re-appear.

(See terminal escape sequences BEFORE_BAR and AFTER_BAR in click).

However the sqlite-utils utils.file_progress context manager wraps click.progressbar and yields an instance of a helper class:

@contextlib.contextmanager     
def file_progress(file, silent=False, **kwargs):
   ...
        with click.progressbar(length=file_length, **kwargs) as bar:
            yield UpdateWrapper(file, bar.update) 

The yielded UpdateWrapper goes out of scope quickly and click.progressbar.__exit__() is called. The cursor is made un-invisible. Hoewever bar is still live and so when the caller iterates on the yielded wrapper this invokes the bar's update method, calling render_progress(), each time printing the "make cursor invisible" escape code. The progressbar.__exit__ function is not called again, so the cursor doesn't re-appear.

mcarpenter avatar Feb 24 '23 20:02 mcarpenter

I also ran into this recently. See below for a patch for one possible solution (tested via "it works on my machine", but I don't expect that this behavior would vary a whole lot across terminal emulators and shells). Another possible solution might be to subclass click's ProgressBar to keep the logic within the original context manager. Happy to send a PR or for this patch to serve as the basis for a fix that someone else authors.

diff --git a/sqlite_utils/utils.py b/sqlite_utils/utils.py
index 06c1a4c..530a3a3 100644
--- a/sqlite_utils/utils.py
+++ b/sqlite_utils/utils.py
@@ -147,14 +147,23 @@ def decode_base64_values(doc):
 
 
 class UpdateWrapper:
-    def __init__(self, wrapped, update):
+    def __init__(self, wrapped, update, render_finish):
         self._wrapped = wrapped
         self._update = update
+        self._render_finish = render_finish
 
     def __iter__(self):
-        for line in self._wrapped:
-            self._update(len(line))
-            yield line
+        return self
+
+    def __next__(self):
+        try:
+            line = next(self._wrapped)
+        except StopIteration as e:
+            self._render_finish()
+            raise
+
+        self._update(len(line))
+        return line
 
     def read(self, size=-1):
         data = self._wrapped.read(size)
@@ -178,7 +187,7 @@ def file_progress(file, silent=False, **kwargs):
     else:
         file_length = os.path.getsize(file.name)
         with click.progressbar(length=file_length, **kwargs) as bar:
-            yield UpdateWrapper(file, bar.update)
+            yield UpdateWrapper(file, bar.update, bar.render_finish)
 
 
 class Format(enum.Enum):

jonafato avatar Jun 06 '23 14:06 jonafato

Came here to report this, but instead I'll confirm the issue across two terminal emulators (Gnome Terminal and Alacritty) on Pop_OS! 22.04 (currently based on Ubuntu/Gnome). Also messes up the formatting of the terminal. Can also confirm that reset fixes it until the next sqlite-utils command.

J450n-4-W avatar Jul 18 '23 19:07 J450n-4-W

I confirm the bug, as above, and that @jonafato 's patch fixes it for me. However, it's not the right fix. The problem is that ProgressBar is being used in the wrong way. This also results in two lines being printed instead of one, like this:

  [#######-----------------------------]   20%
  [####################################]  100%%  

The bug is reproducible for me in any terminal, including Gnome Terminal and Guake, and VSCode. With VSCode I can use this launch.json to reproduce it:


{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python: Module",
            "type": "python",
            "request": "launch",
            "module": "sqlite_utils",
            "justMyCode": false,
            "args": ["insert", "test.db", "test", "--csv", "tests/sniff/example1.csv"]
        }
    ]
}

[edit - deleted my analysis of why the current code is wrong, which was confused and confusing]

spookylukey avatar Oct 04 '23 16:10 spookylukey

Here's an animated GIF that demonstrates the bug:

cursor-bug

simonw avatar Nov 04 '23 00:11 simonw

And a GIF of the fix after applying:

  • #598

cursor-fix

simonw avatar Nov 04 '23 00:11 simonw