33import asyncio
44import atexit
55import subprocess
6+ from datetime import datetime , timezone
67
78from tornado .queues import Queue
89from tornado .websocket import WebSocketHandler
9- from traitlets import Bunch , Instance , List , Set , Unicode , observe
10+ from traitlets import Bunch , Instance , List , Set , Unicode , UseEnum , observe
1011from traitlets .config import LoggingConfigurable
1112
1213from . import stdio
14+ from .types import SessionStatus
1315
1416
1517class LanguageServerSession (LoggingConfigurable ):
@@ -42,6 +44,9 @@ class LanguageServerSession(LoggingConfigurable):
4244 default_value = [],
4345 help = "the currently subscribed websockets" ,
4446 )
47+ status = UseEnum (SessionStatus , default_value = SessionStatus .NOT_STARTED )
48+ last_handler_message_at = Instance (datetime , allow_none = True )
49+ last_server_message_at = Instance (datetime , allow_none = True )
4550
4651 _tasks = None
4752
@@ -56,10 +61,24 @@ def __repr__(self): # pragma: no cover
5661 self .languages , self .argv
5762 )
5863
64+ def to_json (self ):
65+ return dict (
66+ languages = self .languages ,
67+ handler_count = len (self .handlers ),
68+ status = self .status .value ,
69+ last_server_message_at = self .last_server_message_at .isoformat ()
70+ if self .last_server_message_at
71+ else None ,
72+ last_handler_message_at = self .last_handler_message_at .isoformat ()
73+ if self .last_handler_message_at
74+ else None ,
75+ )
76+
5977 def initialize (self ):
6078 """ (re)initialize a language server session
6179 """
6280 self .stop ()
81+ self .status = SessionStatus .STARTING
6382 self .init_queues ()
6483 self .init_process ()
6584 self .init_writer ()
@@ -71,9 +90,14 @@ def initialize(self):
7190 for coro in [self ._read_lsp , self ._write_lsp , self ._broadcast_from_lsp ]
7291 ]
7392
93+ self .status = SessionStatus .STARTED
94+
7495 def stop (self ):
7596 """ clean up all of the state of the session
7697 """
98+
99+ self .status = SessionStatus .STOPPING
100+
77101 if self .process :
78102 self .process .terminate ()
79103 self .process = None
@@ -87,6 +111,8 @@ def stop(self):
87111 if self ._tasks :
88112 [task .cancel () for task in self ._tasks ]
89113
114+ self .status = SessionStatus .STOPPED
115+
90116 @observe ("handlers" )
91117 def _on_handlers (self , change : Bunch ):
92118 """ re-initialize if someone starts listening, or stop if nobody is
@@ -99,8 +125,12 @@ def _on_handlers(self, change: Bunch):
99125 def write (self , message ):
100126 """ wrapper around the write queue to keep it mostly internal
101127 """
128+ self .last_handler_message_at = self .now ()
102129 self .to_lsp .put_nowait (message )
103130
131+ def now (self ):
132+ return datetime .now (timezone .utc )
133+
104134 def init_process (self ):
105135 """ start the language server subprocess
106136 """
@@ -139,6 +169,7 @@ async def _broadcast_from_lsp(self):
139169 server
140170 """
141171 async for msg in self .from_lsp :
172+ self .last_server_message_at = self .now ()
142173 for handler in self .handlers :
143174 handler .write_message (msg )
144175 self .from_lsp .task_done ()
0 commit comments