Skip to content

Commit

Permalink
Remove more usage of uri_to_filename (#1796)
Browse files Browse the repository at this point in the history
  • Loading branch information
rwols authored Jan 15, 2022
1 parent bc0c9a2 commit de0ef67
Show file tree
Hide file tree
Showing 12 changed files with 205 additions and 186 deletions.
6 changes: 4 additions & 2 deletions plugin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
from .core.types import ClientConfig
from .core.types import matches_pattern
from .core.url import filename_to_uri
from .core.url import uri_to_filename
from .core.url import parse_uri
from .core.url import uri_to_filename # deprecated
from .core.version import __version__
from .core.views import MarkdownLangMap

Expand All @@ -36,13 +37,14 @@
'MarkdownLangMap',
'matches_pattern',
'Notification',
'parse_uri',
'register_file_watcher_implementation',
'register_plugin',
'Request',
'Response',
'Session',
'SessionBufferProtocol',
'unregister_plugin',
'uri_to_filename',
'uri_to_filename', # deprecated
'WorkspaceFolder',
]
36 changes: 13 additions & 23 deletions plugin/core/edit.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@
from .open import open_file
from .promise import Promise
from .protocol import TextEdit as LspTextEdit, Position
from .typing import List, Dict, Any, Iterable, Optional, Tuple
from .url import uri_to_filename
from .typing import List, Dict, Any, Optional, Tuple
from functools import partial
import operator
import sublime


Expand All @@ -19,17 +17,19 @@ def parse_workspace_edit(workspace_edit: Dict[str, Any]) -> Dict[str, List[TextE
if isinstance(document_changes, list):
for document_change in document_changes:
if 'kind' in document_change:
# TODO: Support resource operations (create/rename/remove)
debug('Ignoring unsupported "resourceOperations" edit type')
continue
uri = document_change.get('textDocument').get('uri')
version = document_change.get('textDocument').get('version')
text_document = document_change["textDocument"]
uri = text_document['uri']
version = text_document.get('version')
text_edit = list(parse_text_edit(change, version) for change in document_change.get('edits'))
changes.setdefault(uri_to_filename(uri), []).extend(text_edit)
changes.setdefault(uri, []).extend(text_edit)
else:
raw_changes = workspace_edit.get('changes')
if isinstance(raw_changes, dict):
for uri, file_changes in raw_changes.items():
changes[uri_to_filename(uri)] = list(parse_text_edit(change) for change in file_changes)
for uri, uri_changes in raw_changes.items():
changes[uri] = list(parse_text_edit(change) for change in uri_changes)
return changes


Expand All @@ -47,24 +47,14 @@ def parse_text_edit(text_edit: LspTextEdit, version: int = None) -> TextEditTupl
)


def sort_by_application_order(changes: Iterable[TextEditTuple]) -> List[TextEditTuple]:
# The spec reads:
# > However, it is possible that multiple edits have the same start position: multiple
# > inserts, or any number of inserts followed by a single remove or replace edit. If
# > multiple inserts have the same position, the order in the array defines the order in
# > which the inserted strings appear in the resulting text.
# So we sort by start position. But if multiple text edits start at the same position,
# we use the index in the array as the key.

return list(sorted(changes, key=operator.itemgetter(0)))


def apply_workspace_edit(window: sublime.Window, changes: Dict[str, List[TextEditTuple]]) -> Promise:
"""Apply workspace edits. This function must be called from the main thread!"""
return Promise.all([open_file(window, fn).then(partial(_apply_edits, edits)) for fn, edits in changes.items()])
"""
DEPRECATED: Use session.apply_workspace_edit_async instead.
"""
return Promise.all([open_file(window, uri).then(partial(apply_edits, edits)) for uri, edits in changes.items()])


def _apply_edits(edits: List[TextEditTuple], view: Optional[sublime.View]) -> None:
def apply_edits(edits: List[TextEditTuple], view: Optional[sublime.View]) -> None:
if view and view.is_valid():
# Text commands run blocking. After this call has returned the changes are applied.
view.run_command("lsp_apply_document_edit", {"changes": edits})
74 changes: 25 additions & 49 deletions plugin/core/open.py
Original file line number Diff line number Diff line change
@@ -1,52 +1,56 @@
from .logging import exception_log
from .promise import PackagedTask
from .promise import Promise
from .promise import ResolveFunc
from .protocol import Range, RangeLsp
from .protocol import DocumentUri
from .protocol import Range
from .protocol import RangeLsp
from .typing import Dict, Tuple, Optional
from .url import uri_to_filename
from .url import parse_uri
from .views import range_to_region
import os
import sublime
import subprocess
import webbrowser


opening_files = {} # type: Dict[str, Tuple[Promise[Optional[sublime.View]], ResolveFunc[Optional[sublime.View]]]]


def open_file(
window: sublime.Window, file_path: str, flags: int = 0, group: int = -1
window: sublime.Window, uri: DocumentUri, flags: int = 0, group: int = -1
) -> Promise[Optional[sublime.View]]:
"""Open a file asynchronously. It is only safe to call this function from the UI thread."""

"""
Open a file asynchronously.
It is only safe to call this function from the UI thread.
The provided uri MUST be a file URI
"""
file = parse_uri(uri)[1]
# window.open_file brings the file to focus if it's already opened, which we don't want.
# So we first check if there's already a view for that file.
view = window.find_open_file(file_path)
view = window.find_open_file(file)
if view:
return Promise.resolve(view)

view = window.open_file(file_path, flags, group)
view = window.open_file(file, flags, group)
if not view.is_loading():
# It's already loaded. Possibly already open in a tab.
return Promise.resolve(view)

# Is the view opening right now? Then return the associated unresolved promise
for fn, value in opening_files.items():
if fn == file_path or os.path.samefile(fn, file_path):
if fn == file or os.path.samefile(fn, file):
# Return the unresolved promise. A future on_load event will resolve the promise.
return value[0]

# Prepare a new promise to be resolved by a future on_load event (see the event listener in main.py)
def fullfill(resolve: ResolveFunc[Optional[sublime.View]]) -> None:
global opening_files
# Save the promise in the first element of the tuple -- except we cannot yet do that here
opening_files[file_path] = (None, resolve) # type: ignore
opening_files[file] = (None, resolve) # type: ignore

promise = Promise(fullfill)
tup = opening_files[file_path]
tup = opening_files[file]
# Save the promise in the first element of the tuple so that the for-loop above can return it
opening_files[file_path] = (promise, tup[1])
opening_files[file] = (promise, tup[1])
return promise


Expand All @@ -56,54 +60,26 @@ def center_selection(v: sublime.View, r: RangeLsp) -> sublime.View:
window = v.window()
if window:
window.focus_view(v)
v.show_at_center(selection)
if int(sublime.version()) >= 4124:
v.show_at_center(selection, animate=False)
else:
# TODO: remove later when a stable build lands
v.show_at_center(selection) # type: ignore
return v


def open_file_and_center(window: sublime.Window, file_path: str, r: Optional[RangeLsp], flags: int = 0,
group: int = -1) -> Promise[Optional[sublime.View]]:
"""Open a file asynchronously and center the range. It is only safe to call this function from the UI thread."""

def center(v: Optional[sublime.View]) -> Optional[sublime.View]:
if v and v.is_valid():
return center_selection(v, r) if r else v
return None

# TODO: ST API does not allow us to say "do not focus this new view"
return open_file(window, file_path, flags, group).then(center)


def open_file_and_center_async(window: sublime.Window, file_path: str, r: Optional[RangeLsp], flags: int = 0,
group: int = -1) -> Promise[Optional[sublime.View]]:
"""Open a file asynchronously and center the range, worker thread version."""
pair = Promise.packaged_task() # type: PackagedTask[Optional[sublime.View]]
sublime.set_timeout(
lambda: open_file_and_center(window, file_path, r, flags, group).then(
lambda view: sublime.set_timeout_async(
lambda: pair[1](view)
)
)
)
return pair[0]


def open_externally(uri: str, take_focus: bool) -> bool:
"""
A blocking function that invokes the OS's "open with default extension"
"""
if uri.startswith("http:") or uri.startswith("https:"):
return webbrowser.open(uri, autoraise=take_focus)
file = uri_to_filename(uri)
try:
# TODO: handle take_focus
if sublime.platform() == "windows":
# os.startfile only exists on windows, but pyright does not understand sublime.platform().
# TODO: How to make pyright understand platform-specific code with sublime.platform()?
os.startfile(file) # type: ignore
os.startfile(uri) # type: ignore
elif sublime.platform() == "osx":
subprocess.check_call(("/usr/bin/open", file))
subprocess.check_call(("/usr/bin/open", uri))
else: # linux
subprocess.check_call(("xdg-open", file))
subprocess.check_call(("xdg-open", uri))
return True
except Exception as ex:
exception_log("Failed to open {}".format(uri), ex)
Expand Down
Loading

0 comments on commit de0ef67

Please sign in to comment.