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

Require a value when calling resolve function of a Promise #1583

Merged
merged 6 commits into from
Feb 16, 2021
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
6 changes: 3 additions & 3 deletions plugin/core/open.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,11 @@ def center_selection(v: Optional[sublime.View]) -> None:


def open_file_and_center_async(window: sublime.Window, file_path: str, r: Optional[RangeLsp], flag: int = 0,
group: int = -1) -> Promise:
group: int = -1) -> Promise[None]:
"""Open a file asynchronously and center the range, worker thread version."""
return Promise.on_main_thread() \
return Promise.on_main_thread(None) \
.then(lambda _: open_file_and_center(window, file_path, r, flag, group)) \
.then(Promise.on_async_thread)
.then(lambda _: Promise.on_async_thread(None))


def open_externally(uri: str, take_focus: bool) -> bool:
Expand Down
32 changes: 16 additions & 16 deletions plugin/core/promise.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@
import threading

T = TypeVar('T')
TExecutor = TypeVar('TExecutor')
T_contra = TypeVar('T_contra', contravariant=True)
TResult = TypeVar('TResult')


class ResolveFunc(Protocol[T_contra]):
def __call__(self, value: Union[T_contra, 'Promise[T_contra]'] = None) -> None:
def __call__(self, value: Union[T_contra, 'Promise[T_contra]']) -> None:
...


FullfillFunc = Callable[[T], Union[TResult, 'Promise[TResult]', None]]
FullfillFunc = Callable[[T], Union[TResult, 'Promise[TResult]']]
ExecutorFunc = Callable[[ResolveFunc[T]], None]
PackagedTask = Tuple['Promise[T]', ResolveFunc[T]]

Expand Down Expand Up @@ -64,7 +65,7 @@ def process_value(value):
"""

@classmethod
def resolve(cls, resolve_value: T = None) -> 'Promise[T]':
def resolve(cls, resolve_value: T) -> 'Promise[T]':
"""Immediately resolves a Promise.

Convenience function for creating a Promise that gets immediately
Expand All @@ -79,29 +80,29 @@ def executor_func(resolve_fn: ResolveFunc[T]) -> None:
return cls(executor_func)

@classmethod
def on_main_thread(cls, value: T = None) -> 'Promise[T]':
def on_main_thread(cls, value: T) -> 'Promise[T]':
"""Return a promise that resolves on the main thread."""
return Promise(lambda resolve: sublime.set_timeout(lambda: resolve(value)))

@classmethod
def on_async_thread(cls, value: T = None) -> 'Promise[T]':
def on_async_thread(cls, value: T) -> 'Promise[T]':
"""Return a promise that resolves on the worker thread."""
return Promise(lambda resolve: sublime.set_timeout_async(lambda: resolve(value)))

@classmethod
def packaged_task(cls) -> PackagedTask[T]:

class Executor:
class Executor(Generic[TExecutor]):

__slots__ = ("resolver",)

def __init__(self) -> None:
self.resolver = None # type: Optional[ResolveFunc[T]]
self.resolver = None # type: Optional[ResolveFunc[TExecutor]]

def __call__(self, resolver: ResolveFunc[T]) -> None:
def __call__(self, resolver: ResolveFunc[TExecutor]) -> None:
self.resolver = resolver

executor = Executor()
executor = Executor() # type: Executor[T]
promise = cls(executor)
assert callable(executor.resolver)
return promise, executor.resolver
Expand All @@ -121,14 +122,14 @@ def all(cls, promises: List['Promise[T]']) -> 'Promise[List[T]]':
def executor(resolve: ResolveFunc[List[T]]) -> None:
was_resolved = False

def recheck_resolve_status(_: Optional[T]) -> None:
def recheck_resolve_status(_: T) -> None:
nonlocal was_resolved
# We're being called from a Promise that is holding a lock so don't try to use
# any methods that would try to acquire it.
if not was_resolved and all(p.resolved for p in promises):
was_resolved = True
values = [p.value for p in promises]
resolve(values) # type: ignore
resolve(values)

for p in promises:
assert isinstance(p, Promise)
Expand All @@ -146,7 +147,6 @@ def __init__(self, executor_func: ExecutorFunc[T]) -> None:
It gets passed a "resolve" function. The "resolve" function, when
called, resolves the Promise with the value passed to it.
"""
self.value = None # type: Optional[T]
self.resolved = False
self.mutex = threading.Lock()
self.callbacks = [] # type: List[ResolveFunc[T]]
Expand All @@ -157,7 +157,7 @@ def __repr__(self) -> str:
return 'Promise({})'.format(self.value)
return 'Promise(<pending>)'

def then(self, onfullfilled: FullfillFunc[Optional[T], TResult]) -> 'Promise[TResult]':
def then(self, onfullfilled: FullfillFunc[T, TResult]) -> 'Promise[TResult]':
"""Create a new promise and chain it with this promise.

When this promise gets resolved, the callback will be called with the
Expand All @@ -168,7 +168,7 @@ def then(self, onfullfilled: FullfillFunc[Optional[T], TResult]) -> 'Promise[TRe
Arguments:
onfullfilled: The callback to call when this promise gets resolved.
"""
def callback_wrapper(resolve_fn: ResolveFunc[TResult], resolve_value: Optional[T]) -> None:
def callback_wrapper(resolve_fn: ResolveFunc[TResult], resolve_value: T) -> None:
"""A wrapper called when this promise resolves.

Arguments:
Expand All @@ -179,7 +179,7 @@ def callback_wrapper(resolve_fn: ResolveFunc[TResult], resolve_value: Optional[T
# If returned value is a promise then this promise needs to be
# resolved with the value of returned promise.
if isinstance(result, Promise):
result.then(resolve_fn)
result.then(lambda value: resolve_fn(value))
else:
resolve_fn(result)

Expand Down Expand Up @@ -221,6 +221,6 @@ def _is_resolved(self) -> bool:
with self.mutex:
return self.resolved

def _get_value(self) -> Optional[T]:
def _get_value(self) -> T:
with self.mutex:
return self.value
16 changes: 8 additions & 8 deletions plugin/core/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -985,7 +985,7 @@ def execute_command(self, command: ExecuteCommandParams, progress: bool) -> Prom
if self._plugin:
task = Promise.packaged_task() # type: PackagedTask[None]
promise, resolve = task
if self._plugin.on_pre_server_command(command, resolve):
if self._plugin.on_pre_server_command(command, lambda: resolve(None)):
return promise
# TODO: Our Promise class should be able to handle errors/exceptions
return Promise(
Expand Down Expand Up @@ -1020,15 +1020,15 @@ def _maybe_resolve_code_action(self, code_action: CodeAction) -> Promise[Union[C
return self.send_request_task(request)
return Promise.resolve(code_action)

def _apply_code_action_async(self, code_action: Union[CodeAction, Error, None]) -> Promise:
def _apply_code_action_async(self, code_action: Union[CodeAction, Error, None]) -> Promise[None]:
if not code_action:
return Promise.resolve()
return Promise.resolve(None)
if isinstance(code_action, Error):
# TODO: our promise must be able to handle exceptions (or, wait until we can use coroutines)
self.window.status_message("Failed to apply code action: {}".format(code_action))
return Promise.resolve()
return Promise.resolve(None)
edit = code_action.get("edit")
promise = self._apply_workspace_edit_async(edit) if edit else Promise.resolve()
promise = self._apply_workspace_edit_async(edit) if edit else Promise.resolve(None)
command = code_action.get("command")
if isinstance(command, dict):
execute_command = {
Expand All @@ -1038,15 +1038,15 @@ def _apply_code_action_async(self, code_action: Union[CodeAction, Error, None])
return promise.then(lambda _: self.execute_command(execute_command, False))
return promise

def _apply_workspace_edit_async(self, edit: Any) -> Promise:
def _apply_workspace_edit_async(self, edit: Any) -> Promise[None]:
"""
Apply workspace edits, and return a promise that resolves on the async thread again after the edits have been
applied.
"""
changes = parse_workspace_edit(edit)
return Promise.on_main_thread() \
return Promise.on_main_thread(None) \
.then(lambda _: apply_workspace_edit(self.window, changes)) \
.then(Promise.on_async_thread)
.then(lambda _: Promise.on_async_thread(None))

# --- server request handlers --------------------------------------------------------------------------------------

Expand Down