Repository navigation
Show code snippets on demand - #7440
Conversation
|
I just discovered that this doesn't work well with blocking errors and/or daemon. I have a fix but it requires |
|
Some general comments, most of which are copied from #7411: I think that we should fix at least the most obvious problems with column numbers before documenting this as a feature (it's okay to have it as a hidden option though). I have a half-finished PR that fixes column numbers of arguments and may be able to finish that up soon. Also, assignments should probably point to the rvalue. Other comments:
|
Interesting, why does is need |
I actually like the new position more, since we are already taking much more vertical space, why not save a bit of horizontal space? Otherwise the line with wiggly arrow will have few information on it.
It is hard to define what is "token". It seems to me you don't realize how hard would be implementing this to a reasonable degree of reliability and how misleading the positions can be if we will do this in ad-hoc manner. Like we may end up underlining the first item of a tuple while the problem is actually in the type of the second one. I would rather focus on making the start column better first.
OK.
OK.
I want to pass the cache as an argument to the |
JukkaL
left a comment
There was a problem hiding this comment.
Here is a more detailed review of the code (my previous comments were about the formatting of the output). I'm excited that this will be improve usability by making it easier to figure out why mypy is complaining about something.
| decode_python_encoding, DecodeError, trim_source_line, DEFAULT_SOURCE_OFFSET, | ||
| WIGGLY_LINE, DEFAULT_COLUMNS, MINIMUM_WIDTH | ||
| ) | ||
| from mypy.fscache import FileSystemCache |
There was a problem hiding this comment.
I think it would be nice if there was no import from mypy.fscache in this file, and instead we'd accept a callback that reads file contents, for example (dependency injection). We could probably also get rid of the decode_python_encoding and DecodeError imports.
There was a problem hiding this comment.
OK, I was thinking about a protocol, but callback is even better.
| self.show_column_numbers = show_column_numbers | ||
| self.show_error_codes = show_error_codes | ||
| def __init__(self, fscache: Optional[FileSystemCache] = None, | ||
| options: Optional[Options] = None) -> None: |
There was a problem hiding this comment.
This adds a big dependency, which we previously avoided by providing the required information explicitly. It would arguably be cleaner if you'd just add the required options as arguments here instead of passing the whole Options object.
There was a problem hiding this comment.
OK, I will split this back.
| source_lines = None | ||
| if self.options and self.options.show_source_code: | ||
| if self.fscache: | ||
| try: |
There was a problem hiding this comment.
This try statement could be provided as a callback to avoid a depending on fscache.
| ENCODING_RE = \ | ||
| re.compile(br'([ \t\v]*#.*(\r\n?|\n))??[ \t\v]*#.*coding[:=][ \t]*([-\w.]+)') # type: Final | ||
|
|
||
| PLAIN_ANSI_DIM = '\x1b[2m' # type: Final |
There was a problem hiding this comment.
Is this portable? Could we get this from curses or something?
There was a problem hiding this comment.
I tried it in couple terminals and it works (because it is ANSI standard). The problem is that although it is a basic ANSI "feature", terminfo files for most default terminals don't have dim termcap entry, so curses doesn't report it. I was thinking about choosing a grey color that would look good on both white and black background, but it is actually not easy, and again most default terminals are 8-color, not 256-color, so we can't get the color code from curses.
There was a problem hiding this comment.
Ok, sounds reasonable. Maybe add a comment about this?
Since this is not on by default, we can iterate on this afterwards even if some things don't work in every possible environment.
| return source_text | ||
|
|
||
|
|
||
| def trim_source_line(line: str, max_len: int, col: int, min_width: int) -> Tuple[str, int]: |
There was a problem hiding this comment.
This function is a great candidate for regular unit tests.
| loc, msg = error.split('error:', maxsplit=1) | ||
| if not self.show_error_codes: | ||
| if self.show_source_code: | ||
| # Improve readability by wrapping lines when showing source code. |
There was a problem hiding this comment.
It's can be surprising that showing source code also affects how long messages are wrapped. Maybe the option should be named --pretty or something, as it controls a few things.
| elif ': note:' in error: | ||
| loc, msg = error.split('note:', maxsplit=1) | ||
| return loc + self.style('note:', 'blue') + self.underline_link(msg) | ||
| elif self.show_source_code and error.startswith(' ' * DEFAULT_SOURCE_OFFSET): |
There was a problem hiding this comment.
Detecting source code highlights through an indent prefix can be surprising. I wonder if there's a more general way to do this. If there's no alternative simple approach, this should be documented carefully somewhere.
| return res | ||
|
|
||
|
|
||
| def get_term_columns() -> int: |
There was a problem hiding this comment.
What about renaming this to get_terminal_width?
| if add_snippets: | ||
| if severity == 'error' and source_lines and line > 0: | ||
| source_line, offset = trim_source_line(source_lines[line - 1], | ||
| DEFAULT_COLUMNS, column, MINIMUM_WIDTH) |
There was a problem hiding this comment.
Should this use the terminal width?
| if self.show_source_code: | ||
| # Improve readability by wrapping lines when showing source code. | ||
| pad = len(loc) + len('error: ') | ||
| max_len = get_term_columns() - pad - 1 # compensate for space after 'error:' |
There was a problem hiding this comment.
This is kind of suspicious -- how will this work in the daemon? Should the client pass the terminal width to the server as part of each request, since the terminal may change between invocations?
Anyway, it might be better if this was provided by the caller.
There was a problem hiding this comment.
This is kind of suspicious -- how will this work in the daemon?
I was going to fix this after the other PR lands. Essentially yes, this should be passed together with is_tty from the other PR.
|
@JukkaL thanks for review, I agree with all comments I didn't reply above. |
I think it's okay that the wiggly arrow is on its own line without anything else. The savings in horizontal space will be pretty minor, since error codes tend to be short. Here's some reasoning why I'd rather not add the error code after the wiggly arrow (besides that it subjectively looks odd to me):
Yes, there is a risk of doing something confusing. However, I think that the current approach is also confusing sometimes, especially if the error is at the end of the line, or if the target is a name that is much longer/shorter than the squiggle. I initially thought that it underlines a span of code, and was confused when this wasn't the case. A conservative option would be to only show a caret ( Here are other heuristics that could be built on top of the "caret only" approach that seem reasonably safe bets to me (but I haven't thought about this very carefully):
As long as we can't always find the end span of an AST node, we may want to allow some extra options to be passed along with error messages that may affect how the length of the underlining is determined. For example, if the target expression is an attribute expression |
|
Thanks for the updates! I like how it looks now. |
|
Wow! This looks really good. Thank you for working on it Ivan! |
JukkaL
left a comment
There was a problem hiding this comment.
Looks great! There doesn't seem to be any tests for the dmypy client change -- maybe add at least one? Even if the change is trivial, it might get more complex in the future.
| "Long[Type, Names]" are never split. | ||
| ^^^^-------------------------------------------------- | ||
| num_indent max_len | ||
| """ |
There was a problem hiding this comment.
I like the detailed comment!

This essentially fixes #7411
Couple ideas I think may make the much larger amount of output more readable are:
An example output (dark terminal on Linux):