Skip to content
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

Handle custom URI schemes in hover text links #2339

Merged
merged 7 commits into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 23 additions & 22 deletions plugin/core/open.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,36 +21,37 @@
FRAGMENT_PATTERN = re.compile(r'^L?(\d+)(?:,(\d+))?(?:-L?(\d+)(?:,(\d+))?)?')


def lsp_range_from_uri_fragment(fragment: str) -> Optional[Range]:
match = FRAGMENT_PATTERN.match(fragment)
if match:
selection = {'start': {'line': 0, 'character': 0}, 'end': {'line': 0, 'character': 0}} # type: Range
# Line and column numbers in the fragment are assumed to be 1-based and need to be converted to 0-based
# numbers for the LSP Position structure.
start_line, start_column, end_line, end_column = [max(0, int(g) - 1) if g else None for g in match.groups()]
if start_line:
selection['start']['line'] = start_line
selection['end']['line'] = start_line
if start_column:
selection['start']['character'] = start_column
selection['end']['character'] = start_column
if end_line:
selection['end']['line'] = end_line
selection['end']['character'] = UINT_MAX
if end_column is not None:
selection['end']['character'] = end_column
return selection
return None


def open_file_uri(
window: sublime.Window, uri: DocumentUri, flags: int = 0, group: int = -1
) -> Promise[Optional[sublime.View]]:

def parse_fragment(fragment: str) -> Optional[Range]:
match = FRAGMENT_PATTERN.match(fragment)
if match:
selection = {'start': {'line': 0, 'character': 0}, 'end': {'line': 0, 'character': 0}} # type: Range
# Line and column numbers in the fragment are assumed to be 1-based and need to be converted to 0-based
# numbers for the LSP Position structure.
start_line, start_column, end_line, end_column = [max(0, int(g) - 1) if g else None for g in match.groups()]
if start_line:
selection['start']['line'] = start_line
selection['end']['line'] = start_line
if start_column:
selection['start']['character'] = start_column
selection['end']['character'] = start_column
if end_line:
selection['end']['line'] = end_line
selection['end']['character'] = UINT_MAX
if end_column is not None:
selection['end']['character'] = end_column
return selection
return None

decoded_uri = unquote(uri) # decode percent-encoded characters
parsed = urlparse(decoded_uri)
open_promise = open_file(window, decoded_uri, flags, group)
if parsed.fragment:
selection = parse_fragment(parsed.fragment)
selection = lsp_range_from_uri_fragment(parsed.fragment)
if selection:
return open_promise.then(lambda view: _select_and_center(view, cast(Range, selection)))
return open_promise
Expand Down
20 changes: 15 additions & 5 deletions plugin/core/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1622,13 +1622,13 @@ def run_code_action_async(
return self._maybe_resolve_code_action(code_action) \
.then(lambda code_action: self._apply_code_action_async(code_action, view))

def open_uri_async(
def try_open_uri_async(
self,
uri: DocumentUri,
r: Optional[Range] = None,
flags: int = 0,
group: int = -1
) -> Promise[Optional[sublime.View]]:
) -> Optional[Promise[Optional[sublime.View]]]:
if uri.startswith("file:"):
return self._open_file_uri_async(uri, r, flags, group)
# Try to find a pre-existing session-buffer
Expand All @@ -1642,7 +1642,17 @@ def open_uri_async(
# There is no pre-existing session-buffer, so we have to go through AbstractPlugin.on_open_uri_async.
if self._plugin:
return self._open_uri_with_plugin_async(self._plugin, uri, r, flags, group)
return Promise.resolve(None)
return None

def open_uri_async(
self,
uri: DocumentUri,
r: Optional[Range] = None,
flags: int = 0,
group: int = -1
) -> Promise[Optional[sublime.View]]:
promise = self.try_open_uri_async(uri, r, flags, group)
return Promise.resolve(None) if promise is None else promise

def _open_file_uri_async(
self,
Expand All @@ -1668,7 +1678,7 @@ def _open_uri_with_plugin_async(
r: Optional[Range],
flags: int,
group: int,
) -> Promise[Optional[sublime.View]]:
) -> Optional[Promise[Optional[sublime.View]]]:
# I cannot type-hint an unpacked tuple
pair = Promise.packaged_task() # type: PackagedTask[Tuple[str, str, str]]
# It'd be nice to have automatic tuple unpacking continuations
Expand All @@ -1693,7 +1703,7 @@ def open_scratch_buffer(title: str, content: str, syntax: str) -> None:

pair[0].then(lambda tup: sublime.set_timeout(lambda: open_scratch_buffer(*tup)))
return result[0]
return Promise.resolve(None)
return None

def open_location_async(self, location: Union[Location, LocationLink], flags: int = 0,
group: int = -1) -> Promise[Optional[sublime.View]]:
Expand Down
10 changes: 10 additions & 0 deletions plugin/hover.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from .code_actions import actions_manager
from .code_actions import CodeActionOrCommand
from .code_actions import CodeActionsByConfigName
from .core.open import lsp_range_from_uri_fragment
from .core.open import open_file_uri
from .core.open import open_in_browser
from .core.promise import Promise
Expand Down Expand Up @@ -36,6 +37,7 @@
from .core.views import update_lsp_popup
from .session_view import HOVER_HIGHLIGHT_KEY
from functools import partial
from urllib.parse import urlparse
import html
import mdpopups
import sublime
Expand Down Expand Up @@ -362,6 +364,8 @@ def on_select(targets: List[str], idx: int) -> None:
position = {"line": row, "character": col_utf16} # type: Position
r = {"start": position, "end": position} # type: Range
sublime.set_timeout_async(partial(session.open_uri_async, uri, r))
elif urlparse(href).scheme.lower() not in ("", "http", "https"):
sublime.set_timeout_async(partial(self.try_open_custom_uri_async, href))
else:
open_in_browser(href)

Expand All @@ -375,3 +379,9 @@ def run_async() -> None:
session.run_code_action_async(actions[index], progress=True, view=self.view)

sublime.set_timeout_async(run_async)

def try_open_custom_uri_async(self, href: str) -> None:
r = lsp_range_from_uri_fragment(urlparse(href).fragment)
for session in self.sessions():
if session.try_open_uri_async(href, r) is not None:
return
rwols marked this conversation as resolved.
Show resolved Hide resolved