Repository: pallets/werkzeug

Issue:
Highlight user code in debugger
As discussed in :
https://github.com/pallets/flask/issues/2452 

It could be a good feature to highlight blocks relating to user's code in the call stack dump (i.e. in a `flask run` scenario in debug mode).

e.g. in the following screenshot:

![image](https://user-images.githubusercontent.com/328294/29567848-532e9866-8746-11e7-9c2b-779e588a4352.png)

The user must fix the code relating to the highlighted line. Often finding these lines takes more time than necessary. I suggest some simple improvements could be made to help, i.e. detecting files listed in `FLASK_APP`, and highlight any code lines relating to those files with a faded red background.


Discussion:
I don't think it would be very straightforward to detect the lines that need to be highlighted:

- Highlight only line with `FLASK_APP` target in it?
- Highlight all the lines that don't contain `site-packages`?
- Something else, how?

Wouldn't supporting all the different combinations of environments (inside/outside virtualenv, develop mode, Windows/linux, etc.) be tricky?
I think looking for `site-packages` in the name should work for most environments. The situations I can think of where this wont work is if you do `pip install project` instead of `pip install -e project`, or on Ubuntu where the system Python uses "dist-packages". Neither should occur during development.

Should be able to loop over the frames and check the filename. Add an extra "library" class when rendering frames with "site-packages" in the filename. Style might need to be played with, but one option is to fade out all library frames unless they're hovered over.

A small heuristic for unusual envs could be to not mark anything if we end up with all user or all library frames.

Resolving patch:
diff --git a/werkzeug/debug/tbtools.py b/werkzeug/debug/tbtools.py
--- a/werkzeug/debug/tbtools.py
+++ b/werkzeug/debug/tbtools.py
@@ -14,6 +14,7 @@
 import sys
 import json
 import inspect
+import sysconfig
 import traceback
 import codecs
 from tokenize import TokenError
@@ -146,7 +147,7 @@
   <h4>File <cite class="filename">"%(filename)s"</cite>,
       line <em class="line">%(lineno)s</em>,
       in <code class="function">%(function_name)s</code></h4>
-  <div class="source">%(lines)s</div>
+  <div class="source %(library)s">%(lines)s</div>
 </div>
 '''
 
@@ -297,7 +298,9 @@ def render_summary(self, include_title=True):
             classes.append('noframe-traceback')
             frames = []
         else:
-            frames = [group.render() for group in self.groups]
+            library_frames = sum(frame.is_library for frame in self.frames)
+            mark_lib = 0 < library_frames < len(self.frames)
+            frames = [group.render(mark_lib=mark_lib) for group in self.groups]
 
         if include_title:
             if self.is_syntax_error:
@@ -410,14 +413,14 @@ def exception(self):
         rv = "".join(buf).strip()
         return to_unicode(rv, "utf-8", "replace")
 
-    def render(self):
+    def render(self, mark_lib=True):
         out = []
         if self.info is not None:
             out.append(u'<li><div class="exc-divider">%s:</div>' % self.info)
         for frame in self.frames:
             out.append(u"<li%s>%s" % (
                 u' title="%s"' % escape(frame.info) if frame.info else u"",
-                frame.render()
+                frame.render(mark_lib=mark_lib)
             ))
         return u"\n".join(out)
 
@@ -460,7 +463,7 @@ def __init__(self, exc_type, exc_value, tb):
             info = to_unicode(info, "utf-8", "replace")
         self.info = info
 
-    def render(self):
+    def render(self, mark_lib=True):
         """Render a single frame in a traceback."""
         return FRAME_HTML % {
             'id':               self.id,
@@ -468,8 +471,15 @@ def render(self):
             'lineno':           self.lineno,
             'function_name':    escape(self.function_name),
             'lines':            self.render_line_context(),
+            "library": "library" if mark_lib and self.is_library else "",
         }
 
+    @cached_property
+    def is_library(self):
+        return any(
+            self.filename.startswith(path) for path in sysconfig.get_paths().values()
+        )
+
     def render_text(self):
         return u'  File "%s", line %s, in %s\n    %s' % (
             self.filename,


Test patch: