-
Notifications
You must be signed in to change notification settings - Fork 1.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
Use a proper parser for link detection #583
Comments
FYI: These particular cases are handled correctly by See big rewrite bug, balanced parentheses bug, current implementation, unittests. |
When someone tackles this issue there are some more cases on the VS Code issue tracking this microsoft/vscode#21125 |
Merging UNC path support into this, eg. |
Will this be fixed anytime soon? It's very common for Windows users to have a username Firstname and Lastname. Meaning it will include spaces. Breaking Xterm.js which in term breaks the terminal that VSCode implements. A project folder in the user folder |
No ETA but it's open to PRs. I imagine the eventually implementation will look similar to this: https://github.com/microsoft/vscode/blob/master/src/vs/editor/common/modes/linkComputer.ts |
Also needs a little thinking around how this affects the existing experimental "link matcher" api. |
Current stateThe way links work right now is that after the viewport has stopped scrolling for 200ms, the links are computed for the current viewport. Embedders can provide a validation callback which allows the embedder to validate the link sometime after the links are computed, note that links are only shown once they are validated. The end result is pretty nice, we can present underlines for all links even when you need a modifier to execute the link, it does however have some fundamental problems:
class Terminal {
registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): number;
deregisterLinkMatcher(matcherId: number): void;
}
interface ILinkMatcherOptions {
matchIndex?: number;
validationCallback?: (uri: string, callback: (isValid: boolean) => void) => void;
tooltipCallback?: (event: MouseEvent, uri: string, location: IViewportRange) => boolean | void;
leaveCallback?: () => void;
priority?: number;
willLinkActivate?: (event: MouseEvent, uri: string) => boolean;
} ProposalThe long standing hope was to move to a "parser-based" link system (#583) but it was never really clear how an addon would provide a parser exactly. Here's my very VS Code API-inspired proposal: class Terminal {
registerLinkProvider(linkProvider: ILinkProvider): IDisposable;
}
interface ILinkProvider {
provideLink(position: IBufferCellPosition, callback: (link: ILink | undefined) => void): void;
}
interface ILink {
range: IBufferRange;
showTooltip(event: MouseEvent, link: string): void;
hideTooltip(event: MouseEvent, link: string): void;
handle(event: MouseEvent, link: string): void;
}
interface IBufferRange {
start: IBufferCellPosition;
end: IBufferCellPosition;
}
interface IBufferCellPosition {
x: number;
y: number;
} The basic idea is that instead of evaluating links whenever scrolling has stopped, only evaluate links for the current cursor position when a hover occurs. While file access is slow en masse, single requests just for mouse movement should be reasonable. Some other things of interest:
Going this route would seemingly fix many of the problems VS Code has with links, namely:
The only downside for this is a slight delay in the link appearing as the computing and validation occurs at hover time. Open questions
|
Would this be an addon? Or part of the existing |
@Tyriar Good idea to move the expensive link matching to the hover state, will def. remove some "lagging" from the viewport pause state. Few ideas/remarks from my side:
|
@jmbockhorst it would be a new
@jerch that's basically where we're headed with decorations #1852, #1653
Can't remember exactly why it was added, maybe to have web links take precedence over local or vice versa?
Yeah guess we should just commit to this, we can use Promises internally but callbacks in the API for consistency.
Pre-styling isn't desirable imo as underline is a perfectly valid state. |
Not sure if I'll have time to get to this in the next couple of months so this is open to PRs if someone want to have a go at implementing a proof of concept. I think we'll want to support both links types until the next major version at which point |
IMO this would be an ideal use-case to refine our
Note that this marker should track the whole buffer, not just the viewport. Once we have that marker that marks a buffer range (sticky), we can add another layer of abstraction that annotates that marked range with additional meaning. For example a link annotation, that references the marker and holds the parsed link. As a last step, a renderer can iterate annotations in the viewport and draw them accordingly. Here is a rough implementation of the linkifier that would use such an API: // listen for buffer changes, we will get an IBufferRange in that indicates
// which part of the buffer was changed
terminal.buffer.onChange((range, buffer) => {
// do the magic, detect links in that range
const links = detectLinks(range, buffer);
// create a marker for those links
for (let link of links) {
const linkMarker = buffer.addMarker(link.range);
const linkAnnotation = buffer.addAnnotation({ type: 'link', data: link.url, marker: marker });
// dispose marker and annotation if you don't want them anymore
// linkMarker.dispose();
// linkAnnotation.dispose();
}
}); I think this approach is ideal for performance. The link detection can be throttled, and only modified ranges in the buffer have to be rescanned. It also cleanly decouples that semantic annotation of buffer ranges from their representation by the renderer. |
@mofux Yeah very good idea. I want to extend this:
to all cells in active viewport* - if a marker partly covers cells of that, its content can still be modified by cursor jumps + writes, thus the marker should break. A bonus would be to re-eval those positions (with extends to left and right), as the new content might form a valid link again. [*] Viewport is not the correct name for that - I mean those bufferlines, that are still reachable by the cursor. Guess we dont even have a name for that. Maybe "editable buffer"? Edit: Note that a |
@jerch Yeah I guess line level changes would be sufficient. We could still fire an
I'd think that a marker only breaks if a write happens within a line that is inside the marked range. The linkify implementation can over or underscan additional lines for link detection.
Maybe "mutable buffer", or "non-scrollback buffer"? 😅 |
Yes, if
Mutable suggests that the scrollback buffer is static/immutable which is not the case for resize. "Cursor-Addressible-Buffer" === CAB? Lol just kidding, all are kinda word monsters... |
I think markers are a little heavy for this when you consider how links will be typically be used; the user types a bunch of stuff then wants to click on a link, that link will likely never be clicked again when it leaves the viewport. Caching the viewport would be good but anything outside of that I think is overkill. I would also invalidate when all lines of the link's range leave the viewport (not the CAB/NSB/MB 😉). |
Yeah your prolly right, markers still have the update burden for ANY buffer scroll, thus we should use them with caution. (Its a big difference for Regarding link detection and decoupling from render state - I think its perfectly fine to announce onHover the current mouse position, a link parser would only need to parse one wrapped line to find links (a link cannot span several real lines). This seems rather cheap to me, if a parser really wants to get more involved it could cache the findings based on line content (to make sure to correctly spot content changes). The cache here could help to debounce parsing from every single onHover report, still I'd give the cache entries rather short lifecycles as re-parsing seems so cheap. (Have not yet looked at #2530 😊) |
Let's call this done with the link provider work which is about to ship in VS Code |
There will always be issues with URL matching using regex as some cases simply can't be caught with regex. An example where regex will fail is including brackets in URLs but only when the brackets are opened within the url:
http://<domain>.com/foo(bar)
(http://<domain>.com/foobar)
http://<domain>.com/foo,bar
http://<domain>.com/foo, other text
c:\Users\X\My Documents
The text was updated successfully, but these errors were encountered: