Skip to content

Commit e7d8ff3

Browse files
committed
start work on reducing interaction with the ST api to (hopefully) reduce latency
also add support for 9o completions via the QueryCmdCompletions action
1 parent fed34f2 commit e7d8ff3

4 files changed

Lines changed: 195 additions & 89 deletions

File tree

gosubl/margo.py

Lines changed: 126 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
from .margo_agent import MargoAgent
44
from .margo_common import OutputLogger, TokenCounter
55
from .margo_render import render, render_src
6-
from .margo_state import State, actions, client_actions, Config, _view_scope_lang, view_is_9o
6+
from .margo_state import State, actions, client_actions, Config, _view_scope_lang, view_is_9o, MgView
77
from collections import namedtuple
88
import glob
99
import os
10+
import shlex
1011
import sublime
12+
import threading
1113
import time
1214

1315
class MargoSingleton(object):
@@ -29,6 +31,9 @@ def __init__(self):
2931
client_actions.CmdOutput: self._handle_act_output,
3032
}
3133

34+
self._views = {}
35+
self._view_lock = threading.Lock()
36+
3237
def render(self, rs=None):
3338
# ST has some locking issues due to its "thread-safe" API
3439
# don't access things like sublime.active_view() directly
@@ -110,35 +115,90 @@ def enabled(self, view):
110115
return lang in self.enabled_for_langs
111116

112117
def can_trigger_event(self, view, allow_9o=False):
113-
if not self.enabled(view):
114-
return False
118+
_pf=_dbg.pf()
115119

116120
if view is None:
117121
return False
118122

119-
if view.is_loading():
123+
if not self.enabled(view):
120124
return False
121125

122-
vs = view.settings()
123-
if allow_9o and view_is_9o(view):
126+
mgv = self.view(view.id(), view=view)
127+
if allow_9o and mgv.is_9o:
124128
return True
125129

126-
if vs.get('is_widget'):
130+
if not mgv.is_file:
127131
return False
128132

129133
return True
130134

135+
def _preload_views(self):
136+
for w in sublime.windows():
137+
for v in w.views():
138+
if v is not None:
139+
self.view(v.id(), view=v)
140+
141+
def view(self, id, view=None):
142+
with self._view_lock:
143+
mgv = self._views.get(id)
144+
145+
if view is not None:
146+
if mgv is None:
147+
mgv = MgView(mg=self, view=view)
148+
self._views[mgv.id] = mgv
149+
else:
150+
mgv.sync(view=view)
151+
152+
return mgv
153+
154+
def _sync_view(self, event, view):
155+
if event in ('pre_close', 'close'):
156+
with self._view_lock:
157+
self._views.pop(view.id(), None)
158+
159+
return
160+
161+
_pf=_dbg.pf(dot=event)
162+
163+
file_ids = []
164+
for w in sublime.windows():
165+
for v in w.views():
166+
file_ids.append(v.id())
167+
168+
self.file_ids = file_ids
169+
170+
self.view(view.id(), view=view)
171+
131172
def event(self, name, view, handler, args):
132-
allow_9o = name in (
133-
)
134-
if not self.can_trigger_event(view, allow_9o=allow_9o):
173+
if view is None:
135174
return None
136175

137-
try:
138-
return handler(*args)
139-
except Exception:
140-
gs.error_traceback('mg.event:%s' % handler)
141-
return None
176+
_pf=_dbg.pf(dot=name)
177+
178+
def handle_event(gt=0):
179+
if gt > 0:
180+
_pf.gt=gt
181+
182+
self._sync_view(name, view)
183+
184+
if not self.can_trigger_event(view):
185+
return None
186+
187+
try:
188+
return handler(*args)
189+
except Exception:
190+
gs.error_traceback('mg.event:%s' % handler)
191+
return None
192+
193+
blocking = (
194+
'pre_save',
195+
'query_completions',
196+
)
197+
198+
if name in blocking:
199+
return handle_event(gt=0.100)
200+
201+
sublime.set_timeout(handle_event)
142202

143203
def agent_starting(self, ag):
144204
if ag is not self.agent:
@@ -172,20 +232,69 @@ def send(self, *, actions=[], cb=None, view=None):
172232
self._send_start()
173233
return self.agent.send(actions=actions, cb=cb, view=view)
174234

235+
def on_new(self, view):
236+
pass
237+
238+
def on_pre_close(self, view):
239+
pass
240+
175241
def on_query_completions(self, view, prefix, locations):
176242
_, lang = _view_scope_lang(view, 0)
177243
if not lang:
178244
return None
179245

180-
rs = self.send(view=view, actions=[actions.QueryCompletions]).wait(0.500)
246+
act = actions.QueryCompletions
247+
if lang == 'cmd-prompt':
248+
act = self._cmd_completions_act(view, prefix, locations)
249+
if not act:
250+
return None
251+
252+
view = gs.active_view(win=view.window())
253+
if view is None:
254+
return None
255+
256+
rs = self.send(view=view, actions=[act]).wait(0.500)
181257
if not rs:
182258
self.out.println('aborting QueryCompletions. it did not respond in time')
183259
return None
184260

261+
if rs.error:
262+
self.out.println('completion error: %s: %s' % (act, rs.error))
263+
return
264+
185265
cl = [c.entry() for c in rs.state.completions]
186266
opts = rs.state.config.auto_complete_opts
187267
return (cl, opts) if opts != 0 else cl
188268

269+
def _cmd_completions_act(self, view, prefix, locations):
270+
pos = locations[0]
271+
line = view.line(pos)
272+
src = view.substr(line)
273+
if '#' not in src:
274+
return None
275+
276+
i = src.index('#')
277+
while src[i] == ' ' or src[i] == '#':
278+
i += 1
279+
280+
src = src[i:]
281+
pos = pos - line.begin() - i
282+
name = ''
283+
args = shlex.split(src)
284+
if args:
285+
name = args[0]
286+
args = args[1:]
287+
288+
act = actions.QueryCmdCompletions.copy()
289+
act['Data'] = {
290+
'Pos': pos,
291+
'Src': src,
292+
'Name': name,
293+
'Args': args,
294+
}
295+
296+
return act
297+
189298
def on_activated(self, view):
190299
self.queue(view=view, actions=[actions.ViewActivated])
191300

@@ -269,6 +378,7 @@ def ext_fn():
269378

270379
def gs_init(_):
271380
mg._ready = True
381+
sublime.set_timeout(mg._preload_views)
272382
mg.start()
273383

274384
def gs_fini(_):

gosubl/margo_state.py

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
actions = NS(**{k: {'Name': k} for k in (
1010
'QueryCompletions',
11+
'QueryCmdCompletions',
1112
'QueryTooltips',
1213
'QueryIssues',
1314
'QueryUserCmds',
@@ -28,10 +29,45 @@
2829
'CmdOutput',
2930
)})
3031

32+
class MgView(sublime.View):
33+
def __init__(self, *, mg, view):
34+
self.mg = mg
35+
self.is_9o = False
36+
self.is_file = False
37+
self.is_widget = False
38+
self.sync(view=view)
39+
40+
def sync(self, *, view):
41+
if view is None:
42+
return
43+
44+
_pf=_dbg.pf(dot=self.id)
45+
self.id = view.id()
46+
self.view = view
47+
self.name = view_name(view)
48+
self.is_file = self.id in self.mg.file_ids
49+
self.is_widget = not self.is_file
50+
51+
def __eq__(self, v):
52+
return self.view == v
53+
54+
def __hash__(self):
55+
return self.id
56+
57+
def __repr__(self):
58+
return repr(vars(self))
59+
60+
def name(self):
61+
return view_name(self.view)
62+
3163
class Config(object):
3264
def __init__(self, m):
65+
efl = m.get('EnabledForLangs')
66+
if not isinstance(efl, list) or len(efl) == 0:
67+
print('MARGO BUG: EnabledForLangs is invalid. It must be a non-empty list, not `%s: %s`' % (type(efl), efl))
68+
3369
self.override_settings = m.get('OverrideSettings') or {}
34-
self.enabled_for_langs = m.get('EnabledForLangs') or []
70+
self.enabled_for_langs = efl or ['*']
3571
self.inhibit_explicit_completions = m.get('InhibitExplicitCompletions') is True
3672
self.inhibit_word_completions = m.get('InhibitWordCompletions') is True
3773
self.auto_complete_opts = 0
@@ -303,17 +339,29 @@ def _view_hash(view):
303339

304340
return 'id=%s,change=%d' % (_view_id(view), view.change_count())
305341

306-
_scope_lang_pat = re.compile(r'source[.]([^\s.]+)')
342+
_scope_lang_pat = re.compile(r'(?:source|text)[.]([^\s.]+)')
307343
def _view_scope_lang(view, pos):
308344
if view is None:
309345
return ('', '')
310346

347+
_pf=_dbg.pf()
311348
scope = view.scope_name(pos).strip().lower()
349+
312350
if view_is_9o(view):
313-
return (scope, 'cmd-promp')
351+
return (scope, 'cmd-prompt')
314352

315353
l = _scope_lang_pat.findall(scope)
316-
lang = l[-1] if l else ''
354+
if not l:
355+
return (scope, '')
356+
357+
blacklist = (
358+
'plain',
359+
'find-in-files',
360+
)
361+
lang = l[-1]
362+
if lang in blacklist:
363+
return (scope, '')
364+
317365
return (scope, lang)
318366

319367
def _view_src(view, lang):
@@ -326,6 +374,9 @@ def _view_src(view, lang):
326374
if not view.is_dirty():
327375
return ''
328376

377+
if view.is_loading():
378+
return ''
379+
329380
if view.size() > MAX_VIEW_SIZE:
330381
return ''
331382

gosubl/margo_sublime.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,30 @@ class MargoEvents(sublime_plugin.EventListener):
1111
def on_query_completions(self, view, prefix, locations):
1212
return mg.event('query_completions', view, mg.on_query_completions, [view, prefix, locations])
1313

14-
def on_activated_async(self, view):
14+
def on_activated(self, view):
1515
return mg.event('activated', view, mg.on_activated, [view])
1616

17-
def on_modified_async(self, view):
17+
def on_modified(self, view):
1818
return mg.event('modified', view, mg.on_modified, [view])
1919

20-
def on_selection_modified_async(self, view):
20+
def on_selection_modified(self, view):
2121
return mg.event('selection_modified', view, mg.on_selection_modified, [view])
2222

2323
def on_pre_save(self, view):
2424
return mg.event('pre_save', view, mg.on_pre_save, [view])
2525

26-
def on_post_save_async(self, view):
26+
def on_post_save(self, view):
2727
return mg.event('post_save', view, mg.on_post_save, [view])
2828

29-
def on_load_async(self, view):
29+
def on_load(self, view):
3030
return mg.event('load', view, mg.on_load, [view])
3131

32+
def on_new(self, view):
33+
return mg.event('new', view, mg.on_new, [view])
34+
35+
def on_pre_close(self, view):
36+
return mg.event('pre_close', view, mg.on_pre_close, [view])
37+
3238
class MargoRenderSrcCommand(sublime_plugin.TextCommand):
3339
def run(self, edit, src):
3440
render_src(self.view, edit, src)

0 commit comments

Comments
 (0)