-
-
Notifications
You must be signed in to change notification settings - Fork 30.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add "os.get_terminal_size()" function #57818
Comments
Please add a function called "os.get_terminal_size()" that should return a tuple "(width, height)". The built-in "argparse" module would directly benefit from this function, as it would wrap the help text correctly. I'm pretty sure many users would also benefit from it. Once this function gets implemented, issue bpo-13041 can be trivially fixed. There are some suggestions about how to implement this feature in issue bpo-8408. [extra feature:] After this function gets implemented, it might be a good idea to implement some kind of API to detect size changes without requiring probing. (or, at least, the documentation should give some directions about how to achieve it) |
Well, do you want to propose a patch yourself? |
The patch is already almost there (in bpo-13041). I'll post an updated version here in a moment. |
Zbyszek, I just looked at 1 and I disagree that the environment variable should have higher precedence. In fact, I believe it should have lower precedence, and should be used as a fallback. Reason? Imagine a program is launched, and thus it has COLUMNS set to some value. While the program is running, the user resizes the terminal. I believe (though I have not tested) that the envvar will keep the old value, and this function would return wrong dimensions. In my opinion, the system-specific code (termios/windll) should be tried first, and then the envvars (as fallback). If all else fails, maybe it would be better to return (None, None) and let the caller of this function handle that. |
http://bugs.python.org/file23241/patch1.1.diff |
Plus, you should provide also heigth, not only width, possibly as a namedtuple: >>> import shutil
>>> shutil.get_terminal_size()
size(width=80, heigth=170)
>>> |
One reply to three comments: [To myself]:
I disagree. $COLUMNS will usually _not_ be set: $ echo $COLUMNS
119
$ ./python -c 'import os; print("COLUMNS" in os.environ)'
False As I wrote before, the shell only sets COLUMNS for use in the shell, without exporting. Giving a precedence to the environment variable (if it is set by the user) allows to do something like this: Anyway, I think that two functions should be provided: a "raw" one, which does the ioctl, and the "normal" one, which queries $COLUMNS and the the "raw" function. If different behaviour is wanted, than just > Also, the Windows implementation should not rely on ctypes.
Of course. For windows I have:
#include <conio.h>
...
GetConsoleScreenBufferInfo(handle, &csbi)
...
OTOH, there's termios and tty, but they are UNIX only. Module curses is also UNIX only, and slightly different topic, because get_terminal_width should be independent of curses.
|
Well... shutil should stand for "shell utilities" and it already contains stuff which is not strictly related to file/directory direct operations (see shutil.disk_usage and upcoming shutil.which). But maybe you're right... I don't know... |
This is proof-of-concept implementation. This adds two modules: termsize (python) and _termsize (C). The first one contains the get_terminal_size user-facing function and namedtuple definition. The second on contains the function query_terminal_size which does the real job. Two simple tests are added. I'm not sure if it is feasible to test with different terminal sizes: it certainly _could_ be done, e.g. by launching an xterm with a specified geometry, but this would be much This was only tested on 64-bit linux, seems to work. I'm not sure how to the configure tests should be done: right now the presence of <sys/ioctl.h> is checked, and it is used. If not available, Would be nice to test on a mac and other systems, but I don't have one available at the moment unfortunately. I think that the python part (termsize.py) should be either merged with os.py (or whatever home we find for this function), or rewritten in C in _termsizemodule.c and only imported into os. But since it hasn't yet been decided where this should go, keeping it separate is easier for now. |
Don't forget the Windows platform... here is an implementation: https://bitbucket.org/hpk42/py/src/980c8d526463/py/_io/terminalwriter.py#cl-284 |
Ok, a couple of general comments:
|
Here's updated version: termsize.diff.1
This version was tested on linux/amd64 and win32 (XP). |
Seems to work also on kfreebsd/debian (with eglibc). |
(review posted on http://bugs.python.org/review/13609/show ) |
Thanks for the review! New version is attached. The code is actually slightly shorter, but Doc/library/os.rst | 52 +++++++++++++++++++
I want to retain the default values, because get_terminal_size() To make it clearer that those are the fallback values, I renamed the
I think that sys.__stdout__ is better, because when stdout is overridden,
I've also improved the docstrings slightly. |
Thanks for the updated patch. |
What I would do:
Also, I'm not sure I like fallback=(80, 25) as default, followed by:
That means we're never going to get an exception.
|
Nice patch :-) I think the two function approach works well. Since you have already checked that termsize is not NULL, Py_DECREF can be used instead of Py_CLEAR. Would it not be better to use sys.__stdout__ instead of 1 in the documentation and to use STDOUT_FILENO instead of 1 in the code? A brief google suggested that Solaris requires sys/termios.h for TIOCGWINSZ. Perhaps also only define TERMSIZE_USE_IOCTL if TIOCGWINSZ is defined.
Like so:
#if defined(HAVE_SYS_IOCTL_H)
#include <sys/ioctl.h>
#if defined(TIOCGWINSZ)
#define TERMSIZE_USE_IOCTL
#else
#define TERMSIZE_USE_NOTIMPLEMENTED
#endif
#elif defined(HAVE_CONIO_H)
#include <windows.h>
#include <conio.h>
#define TERMSIZE_USE_CONIO
#else
#define TERMSIZE_USE_NOTIMPLEMENTED
#endif (didn't check the windows parts) |
Thanks for the reviews!
Will change to "terminal_size".
I think that there is a difference in "importance" -- shutil.disk_usage cannot be This same argument goes for defining the function only if an implementation is available:
-----------------------------------------------------------------------------------
#if defined(HAVE_SYS_TERMIOS_H)
#include <sys/termios.h>
#if defined(TIOCGWINSZ)
#define TERMSIZE_USE_IOCTL
#else
#define TERMSIZE_USE_NOTIMPLEMENTED
|
I'll try & investigate Solaris a bit... Also, what should be the default terminal size? Gnome-terminal and xterm seem to default to 80x24, not 80x25. |
Updated patch termsize.diff.3
I tested this iteration only on linux and windows, but it is not substantially changed, so should still work on *bsd. (I previously |
I haven't read much of this issue, but I strongly dislike the use of named tuples. Either we want people to use named fields, then we should use a regular class (possibly with slots), or we want to define the result as two values, then there should be a plain tuple result. named tuples should only be used as a compatibility mechanism, when the first design used tuples, and it was later found that additional values need to be returned which would change the number of values (or the original design was considered bad because it returned too many positional values to properly keep track). |
|
The point of named tuples here is that you can use both |
Some comments about termsize.diff.3. I don't see why there are two functions, one should be enough: get_terminal_size() should be dropped, and query_terminal_size() renamed to get_terminal_size(). As said before, I don't think that reading ROWS and COLUMNS environment variables is useful. If a program chose to rely on these variables, it can reimplement its own "try env var or fallback on get_terminal_size()" function. get_terminal_size() should not have a fallback value: it should raise an error, and the caller is responsible to decide what to do in this case (e.g. catch the exception and use its own default value). Most functions in the posix module work like this, and it avoids the difficult choice of the right default value. (fallback=None is an hack to avoid an exception, it's not the pythonic.) I don't think that it's possible that stdin, stdout and/or stderr have its own terminal. I suppose that the 3 streams are always attached to the same terminal. So I don't see why the function would take an argument. Tell me if I am wrong. Instead of using sys.__stdout__.fileno(), you can directly use 1 because Python always create sys.__stdout__ from the file descriptor 1. I think that a tuple (columns, rows) would be just fine. A namedtuple helps when you have a variable number of fields, or more than 3 fields, but here you just have 2 fields, it's not too much difficult to remember which one contains the columns. I would prefer an optional function, instead of implementing a function raising a NotImplementedError. All other posix functions are defined like this. ioctl() is already exposed in the fcntl module, I don't see a specific test for <sys/ioctl.h> header. It looks like the module is always compiled on Unix, I don't see how fcntl and ioctl are tested in setup.py. I don't think that you need <conio.h> to get GetConsoleScreenBufferInfo(), <windows.h> should be enough. So just check for "#ifdef MS_WINDOWS". Your function is helpful, and it is surprising that nobody proposed to implement it in Python. Some libraries did already implement their own function (like the "py" library). |
I think it can be useful in case the program creates its own
From pythonrun.c:
|
Zitat von Antoine Pitrou <report@bugs.python.org>:
And my point is that we should make the choice which
A class could still have a nice repr. |
Following comments by Martin and Victor, here is next version: termsize.diff.4 Changes:
Non-changes:
Tested on linux and windows XP. |
termsize.diff.7:
+ try: AttributeError should be catched here, not NameError. Or better, check +.. function:: get_terminal_size(fallback=(columns, lines)) Hum, you may document the default value: (80, 24). shutil.get_terminal_size() doesn't allow to choose the file |
Well, right now it's just one function. Functionality which you propose See also http://bugs.python.org/issue13609#msg149627 and bpo-444582 and > Antoine Pitrou added the comment: > STINNER Victor added the comment: > - I would prefer os.get_terminal_size() instead of > - To keep os simple, os.query_terminal_size() can return a simple, > - test_os.py: use @unittest.skipUnless() on TermsizeTests to check if
> Hum, you may document the default value: (80, 24). shutil.get_terminal_size() is tied to COLUMNS and ROWS and thus makes > - "Right now the values are not cached, but this might change." why Thanks for the review and comments! Patch version nine attached: termsize.diff.8 |
What happens with COLUMNS env variables if terminal is resized after the Python script is executed. Will get_terminal_size() return new value? |
|
On Tue, Jan 31, 2012 at 7:48 PM, Zbyszek Szmek <report@bugs.python.org>wrote:
Could you just answer the question or provide a relevant link |
"The env. var. is for overriding." http://bugs.python.org/issue13609#msg149620 |
All right, I've found some time to grep conversation related to COLUMNS/ROWS environment/shell variable. +1 for low level system wrapper to get current stdout console size My user story 001: |
So use os.get_terminal_size()
So don't use shutil.get_terminal_size(), but it looks like their is a |
New changeset c92f8de398ed by Antoine Pitrou in branch 'default': |
The patch is finally committed. Thank you Zbyszek for having been quite patient. |
Thanks for merging! I'll try to keep an eye on the buildbot results, but please add me to |
test_stty_match() should be skipped also when os.isatty(sys.__stdin__.fileno()) is False. |
Hi, looking at the tests, the test should be skipped with 'stty invocation |
Actually this test fails due to another reason. I'm still investigating it. In the meantime, a different bug was found: # stty size | cat
46 157
# python3.3 -c 'import os; print(os.get_terminal_size())' | cat
Traceback (most recent call last):
File "<string>", line 1, in <module>
OSError: [Errno 22] Invalid argument Especially redirection to |
Using strace, I see that stty calls ioctl(TIOCGWINSZ) on stdin (fd=0) Attached patch implements a similar idea. |
Your patch is wrong. It always discards ioctl(stdout), even if it was successful. |
New try (I fixed my email address and the patch). |
It is pretty common for programs to behave differently when run through pipe, even if stdin is on a tty. stty is rather the exception than the rule. E.g. almost all programs disable color when piped explicitly through less. 'dpkg | cat' ignores terminal width. So does git and ls. |
The purpose of os.get_terminal_size() is the same as |
Using my patch, you can use os.get_terminal_size(sys.stdout.fileno()) if you like to get an error if sys.stdout is a pipe. My patch only changes the behaviour if no argument is specified (hum, the "special" behaviour should be documented). Or you can also check explicitly sys.stdout.isatty(). It is just more convinient to fallback to stdin if stdout is not a TTY. |
I have four small questions:
|
On Tue, Feb 14, 2012 at 22:17, Zbyszek Szmek <report@bugs.python.org> wrote:
I propose the following solution: accept either an integer or a |
Wouldn't this be quite unwieldy to use: |
I don't think there's much point in the proposed complications. |
Stdout can be connected to a pipe, e.g to less, which in turn might |
I am closing as fixed. If you want to propose further enhancements, please open a new issue. |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: