Progress
I had really nice chats with @nrc and @pnkfelix this week. The one with @pnkfelix was especially helpful in that we realized I can make an assumption on when the “live” scope ends (I need to move it to the end of the first move, if there is one). Non-lexical lifetimes won’t be implemented in the version of the borrow checker that this proof of concept is based on, and when we move to the MIR implementation, there’s already a MIR item we can use to track the “live” scope, EndRegion. This means implementing on the MIR version early will automatically enable support for non-lexical lifetimes right when it’s implemented in the compiler because it will use the same EndRegion item!
As far as design, I’ve hit roadblocks. Implementing the wrapping outline doesn’t seem to be possible in VS Code (with the current API). The outline property doesn’t behave the same as you’d expect since VS Code optimizes lines and only includes the html for the visible lines, possibly out of order in the html itself. They also don’t expose border properties for specific sides (top, left, bottom, right), so I can’t implement an alternative idea.
That alternative idea is to have 8/9 different styling rules to customize the shape of the wrapping span (for edges and corners of borders and maybe one for interior if we keep the faded shading). Atom makes this accessible by allowing standard CSS styling, so I’m going to head back there to see if I can’t get it to work (sorry for saying impossible previously!).
Another idea I came up with to deal with the many borrows that overlap (in invalid cases or with many valid, immutable borrows) is to add items before and after that number the spans.
I think adding them in every case might be distracting, but we could be smart and only add them when spans cross each other or begin/end at the same location within a file. I’ll have to play around with this.
Here are some screen shots of what I implemented in VS Code:
I changed the behavior from the Atom version where the feature is toggled at the file level. While it’s toggled on, clicking anywhere in the file will attempt to compute the regions. The idea was to incorporate the locking behavior mentioned previously, but I hadn’t gotten around to it yet.

Here’s an example of the “live” region being cut short. Here’s it (should be) quite obvious that the second move is illegal.

Here you can see how only the opening of the function starts the “live” region and not the function parameter itself. After seeing this I think we may need to add yet another region to clarify what exactly is being analyzed.