11import sublime , sublime_plugin
2- import os , tempfile , queue
2+ import os , tempfile , queue , subprocess , threading , json , shlex
33from collections import namedtuple
44import xml .etree .ElementTree as ET
55from .. 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
12242def 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
123353def 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