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

Parse position or selection from link fragment #2049

Merged
merged 10 commits into from
Sep 12, 2022
51 changes: 51 additions & 0 deletions plugin/core/open.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,58 @@
from .protocol import DocumentUri
from .protocol import Range
from .protocol import RangeLsp
from .protocol import UINT_MAX
from .typing import Dict, Tuple, Optional
from .url import parse_uri
from .views import range_to_region
from urllib.parse import unquote, urlparse
import os
import re
import sublime
import subprocess
import webbrowser


opening_files = {} # type: Dict[str, Tuple[Promise[Optional[sublime.View]], ResolveFunc[Optional[sublime.View]]]]
FRAGMENT_PATTERN = re.compile(r'^L?(\d+)(?:,(\d+))?(?:-L?(\d+)(?:,(\d+))?)?')


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

def parse_fragment(fragment: str) -> RangeLsp:
match = FRAGMENT_PATTERN.match(fragment)
selection = {'start': {'line': 0, 'character': 0}, 'end': {'line': 0, 'character': 0}} # type: RangeLsp
if match:
# 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:
selection['end']['character'] = end_column
return selection

decoded_uri = unquote(uri) # decode percent-encoded characters
parsed = urlparse(decoded_uri)
open_promise = open_file(window, decoded_uri, flags, group)
if parsed.fragment:
return open_promise.then(lambda view: _select_and_center(view, parse_fragment(parsed.fragment)))
return open_promise


def _select_and_center(view: Optional[sublime.View], r: RangeLsp) -> Optional[sublime.View]:
if view:
return center_selection(view, r)
return None


def _return_existing_view(flags: int, existing_view_group: int, active_group: int, specified_group: int) -> bool:
Expand Down Expand Up @@ -78,6 +121,14 @@ def center_selection(v: sublime.View, r: RangeLsp) -> sublime.View:
return v


def open_in_browser(uri: str) -> None:
# NOTE: Remove this check when on py3.8.
if not uri.lower().startswith(("http://", "https://")):
uri = "https://" + uri
if not webbrowser.open(uri):
sublime.status_message("failed to open: " + uri)


def open_externally(uri: str, take_focus: bool) -> bool:
"""
A blocking function that invokes the OS's "open with default extension"
Expand Down
1 change: 1 addition & 0 deletions plugin/core/url.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ def parse_uri(uri: str) -> Tuple[str, str]:
if os.name == 'nt':
netloc = url2pathname(parsed.netloc)
path = path.lstrip("\\")
path = re.sub(r"^/([a-zA-Z]:)", r"\1", path) # remove slash preceding drive letter
path = re.sub(r"^([a-z]):", _uppercase_driveletter, path)
if netloc:
# Convert to UNC path
Expand Down
18 changes: 4 additions & 14 deletions plugin/document_link.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
from .core.logging import debug
from .core.open import open_file_uri
from .core.open import open_in_browser
from .core.protocol import DocumentLink, Request
from .core.registry import get_position
from .core.registry import LspTextCommand
from .core.typing import Optional
from urllib.parse import unquote, urlparse
import re
import sublime
import webbrowser


class LspOpenLinkCommand(LspTextCommand):
Expand Down Expand Up @@ -57,15 +56,6 @@ def open_target(self, target: str) -> None:
if target.startswith("file:"):
window = self.view.window()
if window:
decoded = unquote(target) # decode percent-encoded characters
parsed = urlparse(decoded)
filepath = parsed.path
if sublime.platform() == "windows":
filepath = re.sub(r"^/([a-zA-Z]:)", r"\1", filepath) # remove slash preceding drive letter
fn = "{}:{}".format(filepath, parsed.fragment) if parsed.fragment else filepath
window.open_file(fn, flags=sublime.ENCODED_POSITION)
open_file_uri(window, target)
else:
if not (target.lower().startswith("http://") or target.lower().startswith("https://")):
target = "http://" + target
if not webbrowser.open(target):
sublime.status_message("failed to open: " + target)
open_in_browser(target)
20 changes: 4 additions & 16 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 .core.logging import debug
from .core.open import open_file_uri
from .core.open import open_in_browser
from .core.promise import Promise
from .core.protocol import Diagnostic
from .core.protocol import DocumentLink
Expand Down Expand Up @@ -37,12 +38,9 @@
from .core.views import unpack_href_location
from .core.views import update_lsp_popup
from .session_view import HOVER_HIGHLIGHT_KEY
from urllib.parse import unquote, urlparse
import functools
import html
import re
import sublime
import webbrowser


SUBLIME_WORD_MASK = 515
Expand Down Expand Up @@ -330,13 +328,7 @@ def _on_navigate(self, href: str, point: int) -> None:
elif href.startswith("file:"):
window = self.view.window()
if window:
decoded = unquote(href) # decode percent-encoded characters
parsed = urlparse(decoded)
filepath = parsed.path
if sublime.platform() == "windows":
filepath = re.sub(r"^/([a-zA-Z]:)", r"\1", filepath) # remove slash preceding drive letter
fn = "{}:{}".format(filepath, parsed.fragment) if parsed.fragment else filepath
window.open_file(fn, flags=sublime.ENCODED_POSITION)
open_file_uri(window, href)
elif href.startswith('code-actions:'):
_, config_name = href.split(":")
actions = self._actions_by_config[config_name]
Expand All @@ -360,11 +352,7 @@ def _on_navigate(self, href: str, point: int) -> None:
r = {"start": position, "end": position} # type: RangeLsp
sublime.set_timeout_async(functools.partial(session.open_uri_async, uri, r))
else:
# NOTE: Remove this check when on py3.8.
if not (href.lower().startswith("http://") or href.lower().startswith("https://")):
href = "http://" + href
if not webbrowser.open(href):
debug("failed to open:", href)
open_in_browser(href)

def handle_code_action_select(self, config_name: str, index: int) -> None:
if index > -1:
Expand Down