From 0d10dbf2f932a89a32e72d50c43eccd8cf0be59d Mon Sep 17 00:00:00 2001 From: Nikita Prokopov Date: Fri, 23 Aug 2024 18:54:28 +0200 Subject: [PATCH] Connect commands now accept `timeout` argument --- CHANGELOG.md | 1 + cs_conn.py | 30 +++++++++++++++++++++++++++++- cs_conn_nrepl_jvm.py | 6 +++--- cs_conn_nrepl_raw.py | 11 ++++++----- cs_conn_shadow_cljs.py | 6 +++--- cs_conn_socket_repl.py | 12 ++++++------ 6 files changed, 48 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca78d7b..8636536 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ Other changes: - Settings can now be specified in main `Preferences.sublime-settings` as well. Just prepend `clojure_sublimed_` to each setting’s name. - REPL can detect namespaces with meta on ns form - Detect `.shadow-cljs/nrepl.port` and `.shadow-cljs/socket-repl.port` +- Connect commands now accept `timeout` argument for automation scenarios like “start clojure, start trying to connect to REPL until port is available” ### 3.8.0 - Aug 8, 2024 diff --git a/cs_conn.py b/cs_conn.py index a9b7454..62e3a59 100644 --- a/cs_conn.py +++ b/cs_conn.py @@ -1,4 +1,4 @@ -import os, re, sublime, sublime_plugin +import os, re, sublime, sublime_plugin, threading, time from . import cs_common, cs_eval, cs_eval_status, cs_parser, cs_warn status_key = 'clojure-sublimed-conn' @@ -17,6 +17,9 @@ def __init__(self): self.disconnecting = False self.window = sublime.active_window() + def get_addr(self): + return self.addr() if callable(self.addr) else self.addr + def connect_impl(self): pass @@ -34,6 +37,31 @@ def connect(self): if window := sublime.active_window(): window.status_message(f'Connection failed') + def try_connect_impl(self, timeout): + state = cs_common.get_state(self.window) + t0 = time.time() + attempt = 1 + while time.time() - t0 <= timeout: + time.sleep(0.25) + try: + cs_common.debug('Connection attempt #{} to {}', attempt, self.get_addr()) + self.connect_impl() + state.conn = self + return + except Exception as e: + attempt += 1 + cs_common.error('Giving up after {} sec connecting to {}', round(time.time() - t0, 2), self.get_addr()) + self.disconnect() + if window := sublime.active_window(): + window.status_message(f'Connection failed') + + def try_connect(self, timeout = 0): + state = cs_common.get_state(self.window) + if timeout: + threading.Thread(target = self.try_connect_impl, args=(timeout,)).start() + else: + self.connect() + def ready(self): return bool(self.status and self.status[0] == phases[4]) diff --git a/cs_conn_nrepl_jvm.py b/cs_conn_nrepl_jvm.py index b13bc86..7b7195a 100644 --- a/cs_conn_nrepl_jvm.py +++ b/cs_conn_nrepl_jvm.py @@ -86,7 +86,7 @@ def handle_connect(self, msg): return True elif 5 == msg.get('id') and 'done' in msg.get('status', []): - self.set_status(4, self.addr) + self.set_status(4, self.get_addr()) return True def handle_new_session(self, msg): @@ -136,12 +136,12 @@ def handle_msg(self, msg): or self.handle_lookup(msg) class ClojureSublimedConnectNreplJvmCommand(sublime_plugin.WindowCommand): - def run(self, address): + def run(self, address, timeout = 0): state = cs_common.get_state(self.window) state.last_conn = ('clojure_sublimed_connect_nrepl_jvm', {'address': address}) if address == 'auto': address = self.input({}).initial_text() - ConnectionNreplJvm(address).connect() + ConnectionNreplJvm(address).try_connect(timeout = timeout) def input(self, args): if 'address' not in args: diff --git a/cs_conn_nrepl_raw.py b/cs_conn_nrepl_raw.py index 392d4f4..9bddfef 100644 --- a/cs_conn_nrepl_raw.py +++ b/cs_conn_nrepl_raw.py @@ -16,8 +16,8 @@ def __init__(self, addr): self.output_view = None def connect_impl(self): - self.set_status(0, 'Connecting to {}...', self.addr) - self.socket = cs_common.socket_connect(self.addr) + self.set_status(0, 'Connecting to {}...', self.get_addr()) + self.socket = cs_common.socket_connect(self.get_addr()) self.reader = threading.Thread(daemon=True, target=self.read_loop) self.reader.start() @@ -84,7 +84,7 @@ def interrupt_impl(self, batch_id, id): def handle_connect(self, msg): if 1 == msg.get('id') and 'new-session' in msg: self.session = msg['new-session'] - self.set_status(4, self.addr) + self.set_status(4, self.get_addr()) return True def handle_disconnect(self, msg): @@ -160,12 +160,13 @@ def handle_msg(self, msg): or self.handle_done(msg) class ClojureSublimedConnectNreplRawCommand(sublime_plugin.WindowCommand): - def run(self, address): + def run(self, address, timeout = 0): state = cs_common.get_state(self.window) state.last_conn = ('clojure_sublimed_connect_nrepl_raw', {'address': address}) if address == 'auto': address = self.input({}).initial_text() - ConnectionNreplRaw(address).connect() + while not state.conn: + ConnectionNreplRaw(address).try_connect(timeout = timeout) def input(self, args): if 'address' not in args: diff --git a/cs_conn_shadow_cljs.py b/cs_conn_shadow_cljs.py index ac99c88..b156bc7 100644 --- a/cs_conn_shadow_cljs.py +++ b/cs_conn_shadow_cljs.py @@ -27,7 +27,7 @@ def handle_connect(self, msg): return True elif 2 == msg.get('id') and msg.get('status') == ['done']: - self.set_status(4, self.addr) + self.set_status(4, self.get_addr()) return True def handle_value(self, msg): @@ -85,10 +85,10 @@ def preview(self, text): """) class ClojureSublimedConnectShadowCljsCommand(sublime_plugin.WindowCommand): - def run(self, address, build): + def run(self, address, build, timeout = 0): state = cs_common.get_state(self.window) state.last_conn = ('clojure_sublimed_connect_shadow_cljs', {'address': address, 'build': build}) - ConnectionShadowCljs(address, build).connect() + ConnectionShadowCljs(address, build).try_connect(timeout = timeout) def input(self, args): if 'address' in args and 'build' in args: diff --git a/cs_conn_socket_repl.py b/cs_conn_socket_repl.py index de7097f..6bf38f9 100644 --- a/cs_conn_socket_repl.py +++ b/cs_conn_socket_repl.py @@ -30,8 +30,8 @@ def __init__(self, addr): self.closing = False def connect_impl(self): - self.set_status(0, 'Connecting to {}', self.addr) - self.socket = cs_common.socket_connect(self.addr) + self.set_status(0, 'Connecting to {}', self.get_addr()) + self.socket = cs_common.socket_connect(self.get_addr()) self.reader = threading.Thread(daemon=True, target=self.read_loop) self.reader.start() @@ -57,7 +57,7 @@ def read_loop(self): self.handle_msg(msg) else: if '{"tag" "started"}' in line: - self.set_status(4, self.addr) + self.set_status(4, self.get_addr()) started = True except OSError: pass @@ -196,12 +196,12 @@ def handle_msg(self, msg): or self.handle_err(msg) class ClojureSublimedConnectSocketReplCommand(sublime_plugin.WindowCommand): - def run(self, address): + def run(self, address, timeout = 0): state = cs_common.get_state(self.window) state.last_conn = ('clojure_sublimed_connect_socket_repl', {'address': address}) if address == 'auto': - address = self.input({}).initial_text() - ConnectionSocketRepl(address).connect() + address = lambda: self.input({}).initial_text() + ConnectionSocketRepl(address).try_connect(timeout = timeout) def input(self, args): if 'address' not in args: