88from typing import Optional
99import urllib .parse
1010
11- from compile_database import GetFlags
12- from config import ServerConfig
11+ from compile_database import (
12+ GetFlags ,
13+ InferFlagsForSwift ,
14+ filekey ,
15+ newfileForCompileFile ,
16+ )
17+ from config import ServerConfig , env
1318from misc import force_remove , get_mtime
1419
1520logger = logging .getLogger (__name__ )
1621
22+
1723def send (data ):
1824 data_str = json .dumps (data )
1925 logger .debug ("Res <-- %s" , data_str )
@@ -32,6 +38,11 @@ def uri2filepath(uri):
3238 return urllib .parse .unquote (result .path )
3339
3440
41+ def uri2realpath (uri ):
42+ path = uri2filepath (uri )
43+ return os .path .realpath (path )
44+
45+
3546def uptodate (target : str , srcs : list [str ]):
3647 target_mtime = get_mtime (target )
3748 srcs_mtime = (get_mtime (src ) for src in srcs )
@@ -53,12 +64,14 @@ def __init__(self, root_path: str, cache_path):
5364 self .observed_uri = set ()
5465 # background thread to observe changes
5566 self .observed_thread : Optional [Thread ] = None
67+
5668 # {path: mtime} cache. use to find changes
5769 self .observed_info = {self .config .path : get_mtime (self .config .path )}
5870
5971 self .reinit_compile_info ()
60- # NOTE:thread-safety: for state shared by main and background thread,
61- # can only changed in sync_compile_file
72+ # NOTE:thread-safety: for state shared by main and background watch thread,
73+ # can only changed in sync_compile_file, which block all thread and no one access it.
74+ # other time, the shared state is readonly and safe..
6275
6376 def get_compile_file (self , config ):
6477 # isolate xcode generate compile file and manual compile_file
@@ -75,7 +88,7 @@ def reinit_compile_info(self):
7588 """all the compile information may change in background"""
7689
7790 # store use to save compile_datainfo. it will be reload when config changes.
78- self .store = {} # main-thread
91+ self .store = {} # main-thread
7992 self ._compile_file = self .get_compile_file (self .config )
8093 if os .path .exists (self ._compile_file ):
8194 self .compile_file = self ._compile_file
@@ -101,11 +114,40 @@ def compile_lock_path(self):
101114
102115 return output_lock_path (self ._compile_file )
103116
117+ def register_uri (self , uri ):
118+ self .observed_uri .add (uri )
119+
120+ file_path = uri2realpath (uri )
121+ flags = GetFlags (file_path , self .compile_file , store = self .store )
122+
123+ if not flags and env .new_file :
124+ if filekeys := newfileForCompileFile (
125+ file_path , self .compile_file , store = self .store
126+ ):
127+ # add new file success, update options for module files
128+ for v in self .observed_uri :
129+ if filekey (uri2filepath (v )) in filekeys :
130+ self .notify_option_changed (v )
131+ return
132+
133+ if not flags and file_path .endswith (".swift" ):
134+ flags = InferFlagsForSwift (file_path , self .compile_file , store = self .store )
135+
136+ self ._notify_option_changed (uri , self .optionsForFlags (flags ))
137+
138+ def unregister_uri (self , uri ):
139+ self .observed_uri .remove (uri )
140+
104141 def optionsForFile (self , uri ):
105- file_path = uri2filepath (uri )
106- flags = GetFlags (file_path , self .compile_file , store = self .store )[
107- "flags"
108- ] # type: list
142+ file_path = uri2realpath (uri )
143+ flags = GetFlags (file_path , self .compile_file , store = self .store )
144+ if not flags and file_path .endswith (".swift" ):
145+ flags = InferFlagsForSwift (file_path , self .compile_file , store = self .store )
146+ return self .optionsForFlags (flags )
147+
148+ def optionsForFlags (self , flags ):
149+ if flags is None :
150+ return None
109151 try :
110152 workdir = flags [flags .index ("-working-directory" ) + 1 ]
111153 except (IndexError , ValueError ):
@@ -116,19 +158,22 @@ def optionsForFile(self, uri):
116158 }
117159
118160 def notify_option_changed (self , uri ):
119- try :
120- notification = {
121- "jsonrpc" : "2.0" ,
122- "method" : "build/sourceKitOptionsChanged" ,
123- "params" : {
124- "uri" : uri ,
125- "updatedOptions" : self .optionsForFile (uri ),
126- },
127- }
128- send (notification )
129- return True
130- except ValueError as e : # may have other type change register, like target
131- logger .debug (e )
161+ # no clear options?
162+ self ._notify_option_changed (uri , self .optionsForFile (uri ))
163+
164+ def _notify_option_changed (self , uri , options ):
165+ # empty options is nouse and lsp will stop working.., at least there should has a infer flags..
166+ if options is None :
167+ return
168+ notification = {
169+ "jsonrpc" : "2.0" ,
170+ "method" : "build/sourceKitOptionsChanged" ,
171+ "params" : {
172+ "uri" : uri ,
173+ "updatedOptions" : options ,
174+ },
175+ }
176+ send (notification )
132177
133178 def shutdown (self ):
134179 self .observed_thread = None # release to end in subthread
@@ -241,6 +286,7 @@ def update_check_time(path):
241286 def trigger_parse (self , xcpath ):
242287 # FIXME: ensure index_store_path from buildServer.json consistent with parsed .compile file..
243288 import xclog_parser
289+
244290 xclog_parser .hooks_echo_to_log = True
245291
246292 from xclog_parser import parse , OutputLockedError
@@ -397,10 +443,9 @@ def textDocument_registerForChanges(message):
397443 action = message ["params" ]["action" ]
398444 uri = message ["params" ]["uri" ]
399445 if action == "register" :
400- if shared_state .notify_option_changed (uri ):
401- shared_state .observed_uri .add (uri )
446+ shared_state .register_uri (uri )
402447 elif action == "unregister" :
403- shared_state .observed_uri . remove (uri )
448+ shared_state .unregister_uri (uri )
404449
405450 def textDocument_sourceKitOptions (message ):
406451 return {
@@ -438,14 +483,9 @@ def serve():
438483 message = json .loads (raw )
439484 logger .debug ("Req --> " + raw )
440485
441- with lock :
442- response = None
443- handler = dispatch .get (message ["method" ].replace ("/" , "_" ))
444- if handler :
445- response = handler (message )
446- # ignore other notifications
447- elif "id" in message :
448- response = {
486+ def default_response ():
487+ if "id" in message :
488+ return {
449489 "jsonrpc" : "2.0" ,
450490 "id" : message ["id" ],
451491 "error" : {
@@ -454,5 +494,17 @@ def serve():
454494 },
455495 }
456496
497+ with lock :
498+ response = None
499+ handler = dispatch .get (message ["method" ].replace ("/" , "_" ))
500+ if handler :
501+ try :
502+ response = handler (message )
503+ except Exception as e :
504+ logger .exception (f"handle message error: { e } " )
505+ response = default_response ()
506+ else :
507+ # ignore other notifications
508+ response = default_response ()
457509 if response :
458510 send (response )
0 commit comments