Skip to content

Commit 012dcd9

Browse files
improved completions performance with 'flow ide' command
1 parent 2753b35 commit 012dcd9

File tree

5 files changed

+336
-115
lines changed

5 files changed

+336
-115
lines changed

src/commands/can_i_use/can_i_use_data.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

src/libs/flow/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from . import flow
22

33
__all__ = [
4-
"flow"
4+
"flow",
55
]

src/libs/flow/flow.py

Lines changed: 238 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,243 @@
11
import sublime, sublime_plugin
2-
import os, tempfile, queue
2+
import os, tempfile, queue, subprocess, threading, json, shlex
33
from collections import namedtuple
44
import xml.etree.ElementTree as ET
55
from .. import util
6-
from .. import global_vars
6+
from .. import NodeJS
7+
from ..global_vars import *
8+
9+
flow_ide_clients = {}
10+
flow_servers = {}
11+
12+
class CLIRequirements():
13+
filename = ""
14+
project_root = ""
15+
contents = ""
16+
cursor_pos = 0
17+
row = 0
18+
col = 0
19+
20+
def __init__(self, filename, project_root, contents, cursor_pos, row, col):
21+
self.filename = filename
22+
self.project_root = project_root
23+
self.contents = contents
24+
self.cursor_pos = cursor_pos
25+
self.row = row
26+
self.col = col
27+
28+
def get_node_and_flow_path():
29+
30+
node_and_flow_path = []
31+
flow_path = os.path.join(NODE_MODULES_BIN_PATH, "flow")
32+
is_from_bin = True
33+
use_node = True
34+
35+
settings = util.get_project_settings()
36+
if settings and settings["project_settings"]["flow_cli_custom_path"]:
37+
flow_path = settings["project_settings"]["flow_cli_custom_path"]
38+
is_from_bin = False
39+
use_node = False
40+
41+
node = NodeJS(check_local=True)
42+
43+
if sublime.platform() == 'windows' and is_from_bin:
44+
node_and_flow_path = [flow_path+'.cmd']
45+
else :
46+
node_and_flow_path = ([node.node_js_path] if use_node else []) + [flow_path]
47+
48+
return node_and_flow_path
49+
50+
class FlowServer():
51+
52+
def __init__(self, root):
53+
self.process = None
54+
self.root = root
55+
56+
def start(self, options = []):
57+
global flow_servers
58+
59+
if self.root in flow_servers:
60+
print("flow server already running: " + self.root)
61+
return
62+
63+
node_and_flow_path = get_node_and_flow_path()
64+
print("starting flow server: " + str(node_and_flow_path))
65+
si = None
66+
if os.name == "nt":
67+
si = subprocess.STARTUPINFO() # type: ignore
68+
si.dwFlags |= subprocess.SW_HIDE | subprocess.STARTF_USESHOWWINDOW # type: ignore
69+
try:
70+
self.process = subprocess.Popen(
71+
node_and_flow_path + ["server"] + options + [self.root],
72+
stdin=subprocess.PIPE,
73+
stdout=subprocess.PIPE,
74+
stderr=subprocess.PIPE,
75+
cwd=self.root,
76+
startupinfo=si)
77+
78+
flow_servers[self.root] = self
79+
return self.process
80+
81+
except Exception as err:
82+
print("Failed to start flow server: " + str(node_and_flow_path), err)
83+
84+
def stop(self):
85+
global flow_servers
86+
87+
if self.process and self.process.poll() is None:
88+
self.process.kill()
89+
90+
self.process = None
91+
92+
if self.root in flow_servers:
93+
del flow_servers[self.root]
94+
95+
# start default flow server
96+
default_flow_server = FlowServer(root=FLOW_DEFAULT_CONFIG_PATH)
97+
default_flow_server.start()
98+
99+
class FlowIDEServer():
100+
101+
def __init__(self, root):
102+
self.process = None
103+
self.stdio_transport = None
104+
self.root = root
105+
106+
def start_stdio_server(self, on_receive, on_closed, options = []):
107+
global flow_ide_client
108+
109+
self.on_receive = on_receive
110+
self.on_closed = on_closed
111+
112+
if self.root in flow_ide_clients:
113+
print("flow ide server already running: " + self.root)
114+
return
115+
116+
node_and_flow_path = get_node_and_flow_path()
117+
118+
si = None
119+
if os.name == "nt":
120+
si = subprocess.STARTUPINFO() # type: ignore
121+
si.dwFlags |= subprocess.SW_HIDE | subprocess.STARTF_USESHOWWINDOW # type: ignore
122+
123+
args = node_and_flow_path + ["ide", "--protocol", "very-unstable", "--from", "sublime_text"] + options + ["--root", self.root]
124+
print("starting flow ide server: " + str(args))
125+
126+
try:
127+
self.process = subprocess.Popen(
128+
args,
129+
stdin=subprocess.PIPE,
130+
stdout=subprocess.PIPE,
131+
stderr=subprocess.PIPE,
132+
cwd=self.root,
133+
startupinfo=si)
134+
135+
self.stdio_transport = StdioTransport(self.process)
136+
self.stdio_transport.start(self.on_receive, self.on_closed)
137+
138+
flow_ide_clients[self.root] = self
139+
140+
return self.process
141+
142+
except Exception as err:
143+
print("Failed to start flow ide server: " + str(node_and_flow_path), err)
144+
145+
def send(self, message):
146+
if self.stdio_transport:
147+
self.stdio_transport.send(message)
148+
149+
def stop(self):
150+
global flow_ide_clients
151+
152+
if self.process and self.process.poll() is None:
153+
self.process.kill()
154+
155+
self.process = None
156+
157+
if self.root in flow_ide_clients:
158+
del flow_ide_clients[self.root]
159+
160+
def prepare_json_rpc_message(self, json_rpc):
161+
json_rpc = json.dumps(json_rpc)
162+
message = """
163+
Content-Length: """ + str(len(json_rpc) + 1) + """
164+
165+
""" + json_rpc + """
166+
"""
167+
return message
168+
169+
def autocomplete(self, params):
170+
json_rpc = {
171+
"jsonrpc":"2.0",
172+
"method": "autocomplete",
173+
"id": 1,
174+
"params": params
175+
}
176+
177+
message = self.prepare_json_rpc_message(json_rpc)
178+
179+
flow_ide_clients[self.root].send(message)
180+
181+
#
182+
# @tomv564 - LSP plugin
183+
# StdioTransport class from https://github.com/tomv564/LSP/blob/master/plugin/core/rpc.py
184+
#
185+
class StdioTransport():
186+
def __init__(self, process):
187+
self.process = process
188+
189+
def start(self, on_receive, on_closed):
190+
self.on_receive = on_receive
191+
self.on_closed = on_closed
192+
self.stdout_thread = threading.Thread(target=self.read_stdout)
193+
self.stdout_thread.start()
194+
195+
def close(self):
196+
self.process = None
197+
self.on_closed()
198+
199+
def read_stdout(self):
200+
"""
201+
Reads JSON responses from process and dispatch them to response_handler
202+
"""
203+
ContentLengthHeader = b"Content-Length: "
204+
205+
running = True
206+
while running:
207+
running = self.process.poll() is None
208+
209+
try:
210+
content_length = 0
211+
while self.process:
212+
header = self.process.stdout.readline()
213+
if header:
214+
header = header.strip()
215+
if not header:
216+
break
217+
if header.startswith(ContentLengthHeader):
218+
content_length = int(header[len(ContentLengthHeader):])
219+
220+
if (content_length > 0):
221+
content = self.process.stdout.read(content_length)
222+
223+
self.on_receive(content.decode("UTF-8"))
224+
225+
except IOError as err:
226+
self.close()
227+
print("Failure reading stdout", err)
228+
break
229+
230+
print("flow stdout process ended.")
231+
232+
def send(self, message):
233+
if self.process:
234+
try:
235+
self.process.stdin.write(bytes(message, 'UTF-8'))
236+
self.process.stdin.flush()
237+
except (BrokenPipeError, OSError) as err:
238+
print("Failure writing to stdout", err)
239+
self.close()
7240

8-
CLIRequirements = namedtuple('CLIRequirements', [
9-
'filename', 'project_root', 'contents', 'cursor_pos', 'row', 'col'
10-
])
11241

12242
def parse_cli_dependencies(view, **kwargs):
13243
filename = view.file_name()
@@ -21,7 +251,7 @@ def parse_cli_dependencies(view, **kwargs):
21251
if folder_path and os.path.isdir(folder_path) and os.path.isfile(os.path.join(folder_path, '.flowconfig')) :
22252
project_root = folder_path
23253
else :
24-
project_root = global_vars.FLOW_DEFAULT_CONFIG_PATH
254+
project_root = FLOW_DEFAULT_CONFIG_PATH
25255

26256
cursor_pos = 0
27257
if kwargs.get('cursor_pos') :
@@ -122,4 +352,5 @@ def parse_cli_dependencies(view, **kwargs):
122352

123353
def hide_errors(view) :
124354
view.erase_regions('flow_error')
125-
view.erase_status('flow_error')
355+
view.erase_status('flow_error')
356+

0 commit comments

Comments
 (0)