-
Notifications
You must be signed in to change notification settings - Fork 182
/
save_command.py
118 lines (95 loc) · 4.05 KB
/
save_command.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
from .core.registry import LspTextCommand
from .core.typing import Callable, List, Type
from abc import ABCMeta, abstractmethod
import sublime
import sublime_plugin
class SaveTask(metaclass=ABCMeta):
"""
Base class for tasks that run on save.
Note: The whole task runs on the async thread.
"""
DEFAULT_TASK_TIMEOUT = 1000
@classmethod
@abstractmethod
def is_applicable(cls, view: sublime.View) -> bool:
pass
def __init__(self, task_runner: LspTextCommand, on_done: Callable[[], None]):
self._task_runner = task_runner
self._on_done = on_done
self._completed = False
self._cancelled = False
self._status_key = 'lsp_save_task_timeout'
def run_async(self) -> None:
self._erase_view_status()
sublime.set_timeout_async(self._on_timeout, self.get_task_timeout_ms())
def _on_timeout(self) -> None:
if not self._completed and not self._cancelled:
self._set_view_status('LSP: Timeout processing {}'.format(self.__class__.__name__))
self._cancelled = True
self._on_done()
def get_task_timeout_ms(self) -> int:
return self.DEFAULT_TASK_TIMEOUT
def cancel(self) -> None:
self._cancelled = True
def _set_view_status(self, text: str) -> None:
self._task_runner.view.set_status(self._status_key, text)
sublime.set_timeout_async(self._erase_view_status, 5000)
def _erase_view_status(self) -> None:
self._task_runner.view.erase_status(self._status_key)
def _on_complete(self) -> None:
assert not self._completed
self._completed = True
if not self._cancelled:
self._on_done()
def _purge_changes_async(self) -> None:
# Supermassive hack that will go away later.
listeners = sublime_plugin.view_event_listeners.get(self._task_runner.view.id(), [])
for listener in listeners:
if listener.__class__.__name__ == 'DocumentSyncListener':
listener.purge_changes_async() # type: ignore
break
class LspSaveCommand(LspTextCommand):
"""
A command used as a substitute for native save command. Runs code actions and document
formatting before triggering the native save command.
"""
_tasks = [] # type: List[Type[SaveTask]]
@classmethod
def register_task(cls, task: Type[SaveTask]) -> None:
assert task not in cls._tasks
cls._tasks.append(task)
def __init__(self, view: sublime.View) -> None:
super().__init__(view)
self._pending_tasks = [] # type: List[SaveTask]
def run(self, edit: sublime.Edit) -> None:
if self._pending_tasks:
for task in self._pending_tasks:
task.cancel()
self._pending_tasks = []
sublime.set_timeout_async(self._trigger_on_pre_save_async)
for Task in self._tasks:
if Task.is_applicable(self.view):
self._pending_tasks.append(Task(self, self._on_task_completed_async))
if self._pending_tasks:
sublime.set_timeout_async(self._run_next_task_async)
else:
self._trigger_native_save()
def _trigger_on_pre_save_async(self) -> None:
# Supermassive hack that will go away later.
listeners = sublime_plugin.view_event_listeners.get(self.view.id(), [])
for listener in listeners:
if listener.__class__.__name__ == 'DocumentSyncListener':
listener.trigger_on_pre_save_async() # type: ignore
break
def _run_next_task_async(self) -> None:
current_task = self._pending_tasks[0]
current_task.run_async()
def _on_task_completed_async(self) -> None:
self._pending_tasks.pop(0)
if self._pending_tasks:
self._run_next_task_async()
else:
self._trigger_native_save()
def _trigger_native_save(self) -> None:
# Triggered from set_timeout to preserve original semantics of on_pre_save handling
sublime.set_timeout(lambda: self.view.run_command('save', {"async": True}))