Skip to content
Open
Show file tree
Hide file tree
Changes from 232 commits
Commits
Show all changes
249 commits
Select commit Hold shift + click to select a range
699dad3
Test both TCP modes
rwols Apr 13, 2026
908c849
Use enum for 'Mode' in server.py
rwols Apr 18, 2026
b4f0420
Make init_options optional
rwols Apr 18, 2026
76f3c3f
WIP add asyncio to transports
rwols Apr 18, 2026
45d7ea6
Test both TCP modes
rwols Apr 13, 2026
e8fae30
Use enum for 'Mode' in server.py
rwols Apr 18, 2026
c511cec
Make init_options optional
rwols Apr 18, 2026
7438d4a
Use StrEnum
rwols Apr 18, 2026
6a104b0
Merge branch 'chore/add-tests' into feat/asyncio
rwols Apr 19, 2026
d59bb1c
More work on adding asyncio
rwols Apr 20, 2026
c0bea09
Merge branch 'main' into feat/asyncio
rwols Apr 20, 2026
c1fc59c
More work towards asyncio
rwols Apr 22, 2026
e35b611
Remove accidental merge conflict changes
rwols Apr 22, 2026
6cad098
Remove code in server.py, I really screwed up the merge but will reba…
rwols Apr 22, 2026
1597782
refactor Promise.__await__
rwols Apr 22, 2026
cc642f4
Add more comments to _SetTimeoutAsyncExecutor
rwols Apr 22, 2026
cea0e99
Rename Session._invoke_views -> Session._invoke_views_async
rwols Apr 22, 2026
3d30ad4
More work towards asyncio
rwols Apr 28, 2026
472705b
debugging sublime_aio.ViewEventListener
rwols Apr 28, 2026
ea34714
Merge branch 'main' into feat/asyncio
rwols May 2, 2026
069ed50
Fix calls
rwols May 2, 2026
776749a
More fix calls
rwols May 2, 2026
0227877
More work. Some diagnostics show intermittently. Request logic is not…
rwols May 3, 2026
2649a6f
Tweaks to pull diagnostics handling
rwols May 3, 2026
54af917
Restore method names
rwols May 5, 2026
1a4589c
Forgot import
rwols May 5, 2026
900f721
Restore more method names
rwols May 5, 2026
35621cf
ResponseException is not needed, we already have Error
rwols May 8, 2026
dbf4d7d
Merge branch 'main' into feat/asyncio
rwols May 8, 2026
f3b2f84
More rename reverts
rwols May 8, 2026
a9c1692
Add more tracing for understanding why requests are not resolving
rwols May 8, 2026
7004df5
Start fixing up 'goto' functionality
rwols May 9, 2026
a876949
Fix:
rwols May 10, 2026
d27aa93
debugging
rwols May 10, 2026
341ff99
We have something working
rwols May 10, 2026
4ed4e3f
Start fixing diagnostic errors because diagnostics work
rwols May 11, 2026
7aba65e
Remove trace() calls
rwols May 12, 2026
e3ccba2
Remove trace() calls
rwols May 12, 2026
c2c2a8c
Remove trace() calls
rwols May 12, 2026
af561a8
Fixups
rwols May 12, 2026
7260c90
Remove trace() calls and fixup type hints
rwols May 12, 2026
a60ddd2
Reintroduce request_code_actions_async for SessionBuffer
rwols May 12, 2026
23210e3
Consolidate sublime_aio & asyncio functions/classes in plugin/core/ai…
rwols May 12, 2026
73ed644
get_session_buffer_for_uri -> get_session_buffer_for_uri_async
rwols May 12, 2026
6d6e772
Session.session_buffers -> Session.session_buffers_async
rwols May 12, 2026
a84f3dd
_invoke_views_async -> _invoke_views
rwols May 12, 2026
f7702c2
Fix notifications being logged twice
rwols May 12, 2026
bb51929
Fix LspCheckApplicableCommand... I think
rwols May 12, 2026
7d58862
Fix errors in WindowManager.start, and allow async version of LspPlug…
rwols May 12, 2026
e2119a1
Print exceptions from coroutines started from `run_coroutine_threadsafe`
rwols May 13, 2026
ce27231
Ensure plugin_unloaded works as expected
rwols May 13, 2026
a4aba53
Merge branch 'main' into feat/asyncio
rwols May 13, 2026
c215234
Fixup incorrect (old) usage of Session.send_request_async
rwols May 13, 2026
ae081e3
Remove trace() calls from sessions.py
rwols May 13, 2026
e4d3324
Merge branch 'main' into feat/asyncio
rwols May 14, 2026
6e5ca85
Fix type errors in sessions.py
rwols May 14, 2026
4866feb
Review all sublime.set_timeout_async call sites
rwols May 14, 2026
73a658a
Merge branch 'main' into feat/asyncio
rwols May 14, 2026
8a6de63
asyncio.Future, async functions, and Promises are all just Awaitables
rwols May 14, 2026
fcb8717
Fix process args
rwols May 14, 2026
8997d51
Fix most type errors, except for tooling.py
rwols May 14, 2026
5943571
Fix: document link was requested before didOpen
rwols May 14, 2026
7485b90
Rename Files: open files sequentially, as it as before
rwols May 14, 2026
edd3ccd
apply_text_edits: wait at least one UI frame
rwols May 14, 2026
91af0e0
LspPlugin.prefer_async_on_pre_start -> LspPlugin.use_asyncio
rwols May 14, 2026
da1fc1d
Invoke LspPlugin.on_initialize after the `initialized` notification
rwols May 14, 2026
34697be
Update tooling.py for asyncio
rwols May 15, 2026
aa37898
Remove unused imports
rwols May 15, 2026
9335c7d
Fix 'TCP client' mode
rwols May 15, 2026
afde9fb
Compatibility with python 3.8
rwols May 15, 2026
0795ee4
Fix missing import for type checking
rwols May 15, 2026
4659e8d
Add stubs/sublime_aio.pyi
rwols May 15, 2026
76aeba1
Fix formatting
rwols May 15, 2026
66bd43a
Fix all remaining lint errors
rwols May 15, 2026
a143bdf
Fix interface method (why isn't this reported as an error by either p…
rwols May 15, 2026
69fd44d
Fixup interface method of `Manager` (why isn't this reported by eithe…
rwols May 15, 2026
c2a9e30
Fixup wm.handle_show_message: it's not async
rwols May 15, 2026
8835214
Add @override to all methods in WindowManager that implement an inter…
rwols May 15, 2026
668846c
Turn off @deprecation warnings
rwols May 15, 2026
c6a89b1
Fix lint warnings
rwols May 15, 2026
9d8c1b3
The return type of the `Window.handle_show_message` interface method …
rwols May 15, 2026
d271624
Fix reference to task object
rwols May 16, 2026
68b8945
Add function: exceptions_log
rwols May 16, 2026
a3c88ec
Catch possible exception when draining the stream writer
rwols May 16, 2026
3915ca6
Add functions aclosing, gather_and_flatten_exceptions, TaskContainer.…
rwols May 16, 2026
82973a5
Fix for python 3.8 runtime regarding opening files lock
rwols May 16, 2026
1ae40fe
Fixes for python 3.8 runtime, better CancellableInflightStreamingRequ…
rwols May 16, 2026
45bbb05
Fix LSP: Rename
rwols May 16, 2026
998ac1c
Comment out the debug print in the tranports.py because I'm feeling c…
rwols May 16, 2026
9c761d1
Merge branch 'main' into feat/asyncio
rwols May 16, 2026
056359b
LspPlugin.use_asyncio() -> LspPlugin.use_asyncio
rwols May 16, 2026
aca3b34
Fixup incorrect merge resolution in api.py
rwols May 16, 2026
1a6448b
I don't know how this got here.
rwols May 16, 2026
dd2b791
Odds and ends in sessions.py
rwols May 16, 2026
ff9cfd7
Odds and ends: make `@requires_session` compatible with coroutine fun…
rwols May 16, 2026
5e3af9c
WIP refactor tests
rwols May 17, 2026
d91eeda
Convert unit/integration tests to asyncio
rwols May 18, 2026
8676dc4
Fix bugs revealed by tests
rwols May 18, 2026
c6efba4
Merge branch 'main' into feat/asyncio
rwols May 18, 2026
2c2e7b3
Merge branch 'main' into feat/asyncio
rwols May 18, 2026
119fa99
Add runtime check for accidental coroutine continuations
rwols May 18, 2026
9e35cc4
In the process of fixing bugs due to tests revealing bugs
rwols May 19, 2026
8c83c96
Re-introduce classSetUp and classTearDown
rwols May 20, 2026
691d8ea
Fixes for macOS
rwols May 21, 2026
0aa553b
Merge branch 'main' into feat/asyncio
rwols May 21, 2026
e9d65dd
Fixes after merge
rwols May 22, 2026
a8f6ba1
Merge branch 'main' into feat/asyncio
rwols May 23, 2026
a5d67f5
Session.open_scratch_buffer always returns a View
rwols May 23, 2026
09b3d3d
Fix disabling and then enabling configs
rwols May 23, 2026
bc70dee
Simplify executors, and encode/decode JSON on the ST async thread
rwols May 23, 2026
7005ac0
Maybe fix LSP.tests.test_single_document.SingleDocumentTestCase.test_…
rwols May 23, 2026
750b995
Remove debug prints from test code
rwols May 23, 2026
e3fc6e9
Fix remaining tests... hopefully?
rwols May 23, 2026
6817036
Fix lint errors
rwols May 23, 2026
3c90ff9
Revert "Maybe fix LSP.tests.test_single_document.SingleDocumentTestCa…
rwols May 23, 2026
6e7f252
Fix purge_changes_async in rename.py not being called from the asynci…
rwols May 23, 2026
a5e3a6d
Rename run_coroutine_threadsafe -> run_coroutine
rwols May 23, 2026
135aa60
Fix bug in the rename flow when first making a preview and then click…
rwols May 23, 2026
9506bd5
Rename call_soon_threadsafe -> run_in_asyncio_thread
rwols May 23, 2026
0e19738
Remove trace() calls from code_actions.py and save_command.py
rwols May 23, 2026
45ee4dd
Remove duplicated imports from api.py
rwols May 23, 2026
ca61cc6
Fix toggling inlay hints
rwols May 23, 2026
bb41f71
Fix hardcoded value for on-save task timeout
rwols May 23, 2026
1529494
Catch the asyncio.CancelledError that's going to be thrown when cance…
rwols May 23, 2026
1f14cee
Mark SessionBuffer.request_code_actions_async as deprecated
rwols May 23, 2026
baec759
There's no need for that opening_files_lock to be used in boot.py
rwols May 23, 2026
81e3ab8
Merge branch 'main' into feat/asyncio
rwols May 23, 2026
89d8ae9
Simplify types in statement
rwols May 23, 2026
ff28a4e
Fix running code actions with multiple language servers
rwols May 23, 2026
0ab6df7
Prefer apply_workspace_edit over apply_workspace_edit_async
rwols May 24, 2026
4801cd6
Merge branch 'main' into feat/asyncio
rwols May 24, 2026
c7af523
Fix code actions on save
rwols May 24, 2026
f3d0684
Rename: run_in_asyncio_thread -> run_on_asyncio_thread
rwols May 24, 2026
9d2283d
Rename: _run_in_st_thread -> _run_on_st_thread
rwols May 24, 2026
fbfba1c
Rename: run_in_main_thread -> run_on_main_thread
rwols May 24, 2026
e39ccf2
Rename: run_in_async_thread -> run_on_async_thread
rwols May 24, 2026
4081b04
Rename: wait_until_st_state -> wait_until
rwols May 24, 2026
42111fb
Merge branch 'main' into feat/asyncio
rchl May 24, 2026
f7686b1
debug asyncio.InvalidStateError
rwols May 25, 2026
317ff07
Revert "debug asyncio.InvalidStateError"
rwols May 25, 2026
54b99af
Allow PostResponseCallback to be a coroutine object
rwols May 25, 2026
1dbb546
Tighten exception catching in Session._refresh_text_document_content
rwols May 25, 2026
ba14429
Add some comments when we invoke loop.run_in_executor
rwols May 25, 2026
d3a3641
Merge branch 'main' into feat/asyncio
rwols May 25, 2026
eee901a
Revert "Allow PostResponseCallback to be a coroutine object"
rchl May 25, 2026
982079c
simplify
rchl May 25, 2026
a76cc3e
Fix: don't try to resolve a cancelled future
rwols May 25, 2026
be9b11f
Make run_tasks a method of LspTextCommandWithTasks
rwols May 25, 2026
1bbae7a
Merge branch 'main' into feat/asyncio
rwols May 26, 2026
9f8de76
Uncomment line
rwols May 26, 2026
965a8df
Make listener.purge_changes_async async
rwols May 30, 2026
51066cb
For streaming requests, handle the case where the langserver doesn't …
rwols May 30, 2026
dee5cac
Merge branch 'main' into feat/asyncio
rwols May 30, 2026
62c3211
Use explicit None type for CancellableRequest._id
rwols May 30, 2026
5be52cd
Hopefully fix macOS tests?
rwols May 30, 2026
df6f9d0
Expose property CancellableInflightStreamingRequest.is_streaming
rwols May 30, 2026
21763fd
Mark methods for implementation details of CancellableInflightStreami…
rwols May 30, 2026
7659c79
Skip unit tests in macOS CI that move files to the Recycle Bin
rwols May 30, 2026
8a1b2c7
Rename: next_frame -> tick
rwols May 31, 2026
29bff70
Check for future state before resolving it
rwols May 31, 2026
87c14dc
Prefer loop.call_soon_threadsafe over run_on_asyncio_thread if we alr…
rwols May 31, 2026
dac06fc
Add purge_changes_async deprecated method
rwols May 31, 2026
ce217bc
Keep apply_text_edits promise-based for now
rwols May 31, 2026
2dfcd3f
api: make command/uri handlers compatible with async functions too
rwols May 31, 2026
1ad342b
Rename: execute_command -> run_command
rwols May 31, 2026
513e47f
chore: remove unused parameter in docblock
rwols May 31, 2026
7b71e48
Merge branch 'main' into feat/asyncio
rwols May 31, 2026
6dc7758
Fix warning PLW0717 from ruff
rwols Jun 1, 2026
52d6814
Rename: run_on_async_thread -> run_on_worker_thread
rwols Jun 1, 2026
13333f0
asyncio.Lock is not needed in transports.py
rwols Jun 1, 2026
80a6066
Just encode/decode JSON on the asyncio thread for now
rwols Jun 1, 2026
86fbc5e
Fix format-on-paste feature
rwols Jun 1, 2026
e4702f4
Update plugin/formatting.py
rwols Jun 2, 2026
43ddf5a
lint
rwols Jun 2, 2026
ffe524a
Inline MaybeCoroutine type
rwols Jun 2, 2026
76f59a5
Inline redo_later closure
rwols Jun 2, 2026
4b07305
Add method to fake server to get the unused mocks
rwols Jun 4, 2026
5e3e987
Fix code actions ... for real this time?
rwols Jun 4, 2026
74c91b6
When an LspPlugin opts into using asyncio, have the on_pre_start clas…
rwols Jun 4, 2026
c8757eb
Merge branch 'main' into feat/asyncio
rwols Jun 4, 2026
8d4ab58
Remove unused test method
rwols Jun 4, 2026
9e13839
Fix code action test... hopefully?
rwols Jun 4, 2026
b0b2232
It's not necessary to print the warning from the fake server
rwols Jun 4, 2026
ed060ab
refactor usage of LspPlugin.use_asyncio
rwols Jun 5, 2026
1c113d4
Ignore PLW0717 'too many statements in try clause' for now
rwols Jun 6, 2026
4494e7d
Work out why we need two code action response mocks in CodeActionsLis…
rwols Jun 6, 2026
718dd54
Revert "Ignore PLW0717 'too many statements in try clause' for now"
rwols Jun 6, 2026
255824b
Fixup/cleanup more of the code action tests
rwols Jun 7, 2026
350fe3a
fix: SessionBuffer.request_code_actions does not return Error
rwols Jun 7, 2026
9b776af
We cannot make LspPlugin.on_pre_send_request_async async
rwols Jun 7, 2026
80f0e84
Merge branch 'main' into feat/asyncio
rwols Jun 11, 2026
9f2de32
add 'exception_policy' to run_coroutine
rwols Jun 14, 2026
388a1bb
Merge branch 'main' into feat/asyncio
rwols Jun 14, 2026
bf43e7c
deal with cancellation
rwols Jun 14, 2026
8c121fb
Fix lint error
rwols Jun 15, 2026
20e875c
Merge branch 'main' into feat/asyncio
rwols Jun 18, 2026
9c2e4db
Merge branch 'main' into feat/asyncio
rwols Jun 18, 2026
0154b19
Replace custom async test case by the new unittesting.AsyncTestCase
rwols Jun 18, 2026
7976d35
Revert "Replace custom async test case by the new unittesting.AsyncTe…
rwols Jun 19, 2026
027537f
Merge branch 'main' into feat/asyncio
rwols Jun 23, 2026
e3086d6
Merge branch 'main' into feat/asyncio
rwols Jun 23, 2026
487ad83
Merge branch 'main' into feat/asyncio
rwols Jun 25, 2026
ba7e4f1
Return errors by value instead of raising
rwols Jun 29, 2026
1a569c4
Merge branch 'main' into feat/asyncio
rwols Jun 29, 2026
280f4a0
Remove experimental Session.stream method for now
rwols Jul 2, 2026
f42d533
Expose 'run_coroutine' and 'run_on_asyncio_thread'
rwols Jul 2, 2026
fe7ec2e
Actually return errors by value :)
rwols Jul 2, 2026
f1e1721
Fix: checking for 'pending.request' is always True
rwols Jul 2, 2026
f53d84e
Remove subclasses of Error
rwols Jul 2, 2026
7697237
fixup tests due to returning Error as value
rwols Jul 2, 2026
0c9c02b
don't report cancellations...
rwols Jul 2, 2026
f7f46a1
Merge branch 'main' into feat/asyncio
rwols Jul 2, 2026
b0a4181
add function 'run_on_threadpool' to API (that wraps loop.run_in_execu…
rwols Jul 2, 2026
6d06e07
workaround for window/showDocument
rwols Jul 5, 2026
207305a
Revert "workaround for window/showDocument"
rwols Jul 5, 2026
c525bd1
Log __del__
rchl Jul 5, 2026
9a0916c
Merge open_uri and try_open_uri, return sublime.View | None
rwols Jul 6, 2026
b08d315
use sublime_aio.run_in_worker
rwols Jul 6, 2026
20f2991
Improve type hints in aio.py
rwols Jul 6, 2026
08c4c2d
Fix circular references
rwols Jul 6, 2026
4eda32c
Revert "Log __del__"
rwols Jul 6, 2026
c17f86b
Fix potential deadlock
rwols Jul 6, 2026
eeddacc
Merge branch 'main' into feat/asyncio
rwols Jul 6, 2026
220fb18
Merge branch 'main' into feat/asyncio
rwols Jul 6, 2026
cdf2c8f
Merge branch 'main' into feat/asyncio
rwols Jul 11, 2026
d0b059c
return X | BaseException -> return X | Error
rwols Jul 11, 2026
172b05e
Apply timeout per task, instead of a timeout for all the tasks combined
rwols Jul 11, 2026
944bbc4
Tweaks for preparing Session.open_uri to be used at more call sites
rwols Jul 11, 2026
c6d729d
make the handling of non-empty code action lists a bit clearer (I hope)
rwols Jul 11, 2026
439896a
Merge branch 'main' into feat/asyncio
rwols Jul 18, 2026
f96085a
Fix incorrect merge conflict resolution
rwols Jul 20, 2026
42f94d3
Merge branch 'main' into feat/asyncio
rwols Jul 23, 2026
db80534
Update plugin/completion.py (fix auto-imports)
rwols Jul 28, 2026
6d421c6
Allow calling send_request_async from any thread
rwols Jul 28, 2026
dac3276
Add some clarifying comments
rwols Jul 28, 2026
55f5cf3
Update old lingering comments about QueryCompletionsTask
rwols Jul 28, 2026
c9432b9
Add docblock to _MAX_WAIT_ATTEMPTS
rwols Jul 28, 2026
cb2913b
Fix ancient completionItem/resolve rendering bug and simplify
rwols Jul 28, 2026
af4235b
Merge branch 'main' into feat/asyncio
rwols Jul 28, 2026
f67aa98
update docblock for @uri_handler
rwols Jul 29, 2026
73285fd
Fix ancient completionItem/resolve rendering bug, part 2
rwols Jul 30, 2026
c3b112d
Rename: _MAX_WAIT_ATTEMPTS -> _FILE_DELETED_MAX_CHECK_ATTEMPTS
rwols Aug 1, 2026
91c495d
Fix: make Session.{execute_command,run_code_action_async} threadsafe
rwols Aug 2, 2026
936736e
Fix: make DocumentSyncListener.purge_changes_async threadsafe
rwols Aug 2, 2026
9eb5a7b
Merge branch 'main' into feat/asyncio
rwols Aug 10, 2026
e1f441c
Rename: CancellableRequest -> RequestController
rwols Aug 15, 2026
a81bef5
Rename: CancellableInflightRequest -> CancellableRequest
rwols Aug 15, 2026
1d0b821
Merge branch 'main' into feat/asyncio
rwols Aug 15, 2026
02fcfd3
Organize imports
rwols Aug 15, 2026
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
40 changes: 24 additions & 16 deletions boot.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from .plugin.configuration import LspDisableLanguageServerInProjectCommand
from .plugin.configuration import LspEnableLanguageServerGloballyCommand
from .plugin.configuration import LspEnableLanguageServerInProjectCommand
from .plugin.core.aio import run_coroutine
from .plugin.core.constants import ST_VERSION
from .plugin.core.css import load as load_css
from .plugin.core.open import g_opening_files
Expand Down Expand Up @@ -90,10 +91,18 @@
from .plugin.tooling import LspParseVscodePackageJson
from .plugin.tooling import LspTroubleshootServerCommand
from typing import Any
from typing import TYPE_CHECKING
import os
import sublime
import sublime_aio
import sublime_plugin

if TYPE_CHECKING:
import asyncio

# Uncomment to see all invocations that are marked @deprecated in the Console.
# warnings.simplefilter('always', DeprecationWarning)

__all__ = (
"DocumentSyncListener",
"Listener",
Expand Down Expand Up @@ -222,18 +231,18 @@ def show_warning() -> None:

def plugin_unloaded() -> None:
_unregister_all_plugins()
windows.disable()
run_coroutine(windows.disable())
for listeners in sublime_plugin.view_event_listeners.values():
for listener in listeners:
if isinstance(listener, DocumentSyncListener):
listener.before_destroy()
unload_settings()


class Listener(sublime_plugin.EventListener):
class Listener(sublime_aio.EventListener):

def on_exit(self) -> None:
kill_all_subprocesses()
async def on_exit(self) -> None:
await kill_all_subprocesses()

def on_load_project_async(self, window: sublime.Window) -> None:
if manager := windows.lookup(window):
Expand Down Expand Up @@ -262,27 +271,26 @@ def on_pre_move(self, view: sublime.View) -> None:
sublime.set_timeout_async(listener.on_post_move_window_async, 1)
return

def on_load(self, view: sublime.View) -> None:
async def on_load(self, view: sublime.View) -> None:
file_name = view.file_name()
if not file_name:
return
for fn in g_opening_files:
if fn == file_name or os.path.samefile(fn, file_name):
# Remove it from the pending opening files, and resolve the promise.
g_opening_files.pop(fn)[1](view)
break
if future := self._find_opening_file_future(file_name):
future.set_result(view)

def on_pre_close(self, view: sublime.View) -> None:
async def on_pre_close(self, view: sublime.View) -> None:
file_name = view.file_name()
if not file_name:
return
if future := self._find_opening_file_future(file_name):
# The view got closed before it finished loading. This can happen.
future.set_result(None)

def _find_opening_file_future(self, file_name: str) -> asyncio.Future[sublime.View | None] | None:
for fn in g_opening_files:
if fn == file_name or os.path.samefile(fn, file_name):
tup = g_opening_files.pop(fn, None) # noqa: B909
if tup:
# The view got closed before it finished loading. This can happen.
tup[1](None)
break
return g_opening_files.pop(fn, None)
return None

def on_post_window_command(self, window: sublime.Window, command_name: str, args: dict[str, Any] | None) -> None:
if command_name == "show_panel":
Expand Down
1 change: 1 addition & 0 deletions dependencies.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"bracex",
"mdpopups",
"orjson",
"sublime_aio",
"typing_extensions",
"wcmatch"
]
Expand Down
6 changes: 6 additions & 0 deletions plugin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
from .api import request_handler
from .api import unregister_plugin
from .api import uri_handler
from .core.aio import run_coroutine
from .core.aio import run_on_asyncio_thread
from .core.aio import run_on_threadpool
from .core.collections import DottedDict
from .core.constants import MarkdownLangMap
from .core.constants import ST_STORAGE_PATH
Expand Down Expand Up @@ -95,6 +98,9 @@
'register_file_watcher_implementation',
'register_plugin',
'request_handler',
'run_coroutine',
'run_on_asyncio_thread',
'run_on_threadpool',
'unregister_plugin',
'uri_from_view',
'uri_handler',
Expand Down
106 changes: 73 additions & 33 deletions plugin/api.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from __future__ import annotations

from ..protocol import LSPAny
from .core.aio import run_on_threadpool
from .core.constants import ST_STORAGE_PATH
from .core.logging import exception_log
from .core.promise import Promise
from .core.protocol import Response
from .core.settings import client_configs
from .core.types import method2attr
Expand All @@ -15,6 +15,7 @@
from functools import wraps
from pathlib import Path
from typing import Any
from typing import Awaitable
from typing import Callable
from typing import Final
from typing import final
Expand Down Expand Up @@ -54,14 +55,14 @@
# P represents the parameters *after* the 'self' argument
P = TypeVar('P', bound=LSPAny)
R = TypeVar('R', bound=LSPAny)
CommandHandler = Callable[['list[P] | None'], 'Promise[R]']
CommandHandlerForDecorator = Callable[[Any, 'list[P] | None'], 'Promise[R]']
UriHandler = Callable[['DocumentUri', sublime.NewFileFlags], 'Promise[sublime.Sheet | None]']
CommandHandler = Callable[['list[P] | None'], 'Awaitable[R]']
CommandHandlerForDecorator = Callable[[Any, 'list[P] | None'], 'Awaitable[R]']
UriHandler = Callable[['DocumentUri', sublime.NewFileFlags], 'Awaitable[sublime.Sheet | None]']
# Decorator needs a dedicated type with `Any` as the first parameter representing `Self` to make its
# implementation happy. I couldn't find a better way (Concatenate and ParamSpec don't seem to help here).
UriHandlerForDecorator = Callable[[Any, 'DocumentUri', sublime.NewFileFlags], 'Promise[sublime.Sheet | None]']
UriHandlerForDecorator = Callable[[Any, 'DocumentUri', sublime.NewFileFlags], 'Awaitable[sublime.Sheet | None]']
PostResponseCallback = Callable[[], None]
RequestHandlerResponse = Union[Promise[R], Tuple[Promise[R], PostResponseCallback]]
RequestHandlerResponse = Union[R, Tuple[R, PostResponseCallback]]


g_plugins: dict[str, type[AbstractPlugin | LspPlugin]] = {}
Expand Down Expand Up @@ -225,36 +226,41 @@ def decorator(func: Callable[[Any, P], None]) -> Callable[[Any, P], None]:

def request_handler(
method: str
) -> Callable[[Callable[[Any, P], RequestHandlerResponse]], Callable[[Any, P, int], Promise[Response[R]]]]: # pyright: ignore[reportInvalidTypeVarUse]
) -> Callable[[Callable[[Any, P], Awaitable[RequestHandlerResponse]]], Callable[[Any, P, int], Awaitable[Response[R]]]]: # pyright: ignore[reportInvalidTypeVarUse]
"""
Decorator to mark a method as a handler for a specific LSP request.
Decorator to mark a coroutine method as a handler for a specific LSP request.

Usage:
```py
@request_handler('eslint/openDoc')
def on_open_doc(self, params: TextDocumentIdentifier) -> Promise[bool]:
async def on_open_doc(self, params: TextDocumentIdentifier) -> bool:
Comment thread
rchl marked this conversation as resolved.
...
```

The decorated method will be called with the request parameters whenever the specified
request is received from the language server. The method must return a Promise that resolves
to the response value. The framework will automatically send it back to the server.
The decorated coroutine method will be called with the request parameters whenever the specified
request is received from the language server. The coroutine method must return a response value.
The framework will automatically send it back to the server.

An older, but backwards-compatible way to define a request handler is by defining a function that returns a Promise.
While that works, the advice is to define a coroutine function.

:param method: The LSP request method name (e.g., 'eslint/openDoc').
:returns: A decorator that registers the function as a request handler.
:returns: A decorator that registers the coroutine function as a request handler.
"""

def decorator(func: Callable[[Any, P], RequestHandlerResponse]) -> Callable[[Any, P, int], Promise[Response[R]]]:
def decorator(
func: Callable[[Any, P], Awaitable[RequestHandlerResponse]]
) -> Callable[[Any, P, int], Awaitable[Response[R]]]:

@wraps(func)
def wrapper(self: Any, params: P, request_id: int) -> Promise[Response[Any]]:
promise_or_tuple = func(self, params)
if isinstance(promise_or_tuple, tuple):
promise, post_request_callback = promise_or_tuple
async def wrapper(self: Any, params: P, request_id: int) -> Response[Any]:
response = await func(self, params)
if isinstance(response, tuple):
result, post_request_callback = response
else:
promise = promise_or_tuple
result = response
post_request_callback = None
return promise.then(lambda result: Response(request_id, result, post_request_callback))
return Response(request_id, result, post_request_callback)

setattr(wrapper, HANDLER_MARKER, method)
return wrapper
Expand All @@ -272,7 +278,7 @@ def command_handler(command_name: str) -> Callable[[CommandHandlerForDecorator],
Usage:
```py
@command_handler('typescript.rename')
def on_custom_rename(self, arguments: list[LSPAny] | None) -> Promise[LspAny]:
async def on_custom_rename(self, arguments: list[LSPAny] | None) -> LspAny:
...
```

Expand Down Expand Up @@ -453,16 +459,18 @@ def is_applicable_async(cls, context: IsApplicableContext) -> bool:
return False

@classmethod
@deprecated("override on_pre_start instead")
def on_pre_start_async(cls, context: OnPreStartContext) -> None:
pass

@classmethod
async def on_pre_start(cls, context: OnPreStartContext) -> None:
"""
Called just before the language server process is started.

Override to perform any preparation needed before startup - for example installing or updating server binaries,
resolving the working directory, or injecting extra template variables into `context.variables`.

This method runs on a worker thread so perform any blocking I/O (e.g. downloading a binary, running
`npm install`) directly here without spawning additional threads.

Mutations to `context.working_directory` and `context.variables` are picked up and used when launching the
server process.

Expand All @@ -471,7 +479,11 @@ def on_pre_start_async(cls, context: OnPreStartContext) -> None:
:param context: The startup context. `context.configuration`, `context.variables` and
`context.working_directory` can be mutated to influence how the server is launched.
"""
pass
# Historically these methods tended to run relatively slow.
# We don't want to use Sublime's worker thread for this any longer.
# Utilize the default thread pool instead.
# https://docs.python.org/3/library/asyncio-dev.html#running-blocking-code
await run_on_threadpool(cls.on_pre_start_async, context)
Comment thread
rchl marked this conversation as resolved.

def __init__(self, weaksession: ref[Session]) -> None:
"""
Expand All @@ -491,14 +503,18 @@ def __init_subclass__(cls, **kwargs: Any) -> None:
cls.name = cls.__module__.split('.')[0] # pyright: ignore[reportAttributeAccessIssue]
cls.plugin_storage_path = Path(ST_STORAGE_PATH, cls.name) # pyright: ignore[reportAttributeAccessIssue]

@deprecated("override on_initialized instead")
def on_initialized_async(self) -> None:
pass

async def on_initialized(self) -> None:
"""
Called after the `initialized` notification has been sent to the language server.

Override to perform any post-initialization work, such as sending custom notifications or requests
that depend on the server's capabilities reported in the `initialize` response.
"""
pass
self.on_initialized_async()

def on_pre_send_request_async(self, request: ClientRequest, view: sublime.View | None) -> None:
"""
Expand All @@ -509,7 +525,11 @@ def on_pre_send_request_async(self, request: ClientRequest, view: sublime.View |
"""
pass

@deprecated("override on_pre_send_response instead")
def on_pre_send_response_async(self, response: ClientResponse) -> None:
pass

async def on_pre_send_response(self, response: ClientResponse) -> None:
"""
Notifies about a response that is about to be sent to the language server.

Expand All @@ -518,17 +538,25 @@ def on_pre_send_response_async(self, response: ClientResponse) -> None:

:param response: The response object containing 'method', 'params', and 'result'.
"""
pass
self.on_pre_send_response_async(response)

@deprecated("override on_pre_send_notification instead")
def on_pre_send_notification_async(self, notification: ClientNotification) -> None:
pass

async def on_pre_send_notification(self, notification: ClientNotification) -> None:
"""
Notifies about a notification that is about to be sent to the language server.

:param notification: The notification object. The notification['params'] can be modified by the plugin.
"""
pass
self.on_pre_send_notification_async(notification)

@deprecated("override on_server_response instead")
def on_server_response_async(self, response: ServerResponse) -> None:
pass

async def on_server_response(self, response: ServerResponse) -> None:
"""
Notifies about a response message that has been received from the language server.

Expand All @@ -537,25 +565,37 @@ def on_server_response_async(self, response: ServerResponse) -> None:
:param response: The response object to the request. The response['result'] field can be modified by the
plugin, before it gets further handled by the LSP package.
"""
pass
self.on_server_response_async(response)

@deprecated("override on_server_notification instead")
def on_server_notification_async(self, notification: ServerNotification) -> None:
pass

async def on_server_notification(self, notification: ServerNotification) -> None:
"""
Notifies about a notification message that has been received from the language server.

:param notification: The notification object.
"""
pass
self.on_server_notification_async(notification)

@deprecated("override on_text_changed instead")
def on_text_changed_async(self, session_buffer: SessionBufferProtocol) -> None:
"""Called when the content of the session buffer has changed or a new buffer was opened (debounced)."""
pass

async def on_text_changed(self, session_buffer: SessionBufferProtocol) -> None:
"""Called when the content of the session buffer has changed or a new buffer was opened (debounced)."""
self.on_text_changed_async(session_buffer)

def on_selection_modified_async(self, session_view: SessionViewProtocol) -> None:
"""Called after the selection has been modified in a view (debounced)."""
pass

@deprecated("override on_session_end instead")
def on_session_end_async(self, exit_code: int | None, exception: Exception | None) -> None:
pass

async def on_session_end(self, exit_code: int | None, exception: Exception | None) -> None:
"""
Notifies about the session ending (also if the session has crashed). Provides an opportunity to clean up
any stored state or delete references to the session or plugin instance that would otherwise prevent the
Expand All @@ -565,9 +605,9 @@ def on_session_end_async(self, exit_code: int | None, exception: Exception | Non
after this method returns. In this case exit_code and exception are None.
If the session has crashed, the exit_code and an optional exception are provided.

This API is triggered on async thread.
This API is triggered on the asyncio thread.
"""
pass
self.on_session_end_async(exit_code, exception)


@deprecated('Use LspPlugin instead')
Expand Down
Loading