@@ -8,17 +8,42 @@ import { VDomRenderer, VDomModel } from '@jupyterlab/apputils';
88
99import {
1010 interactiveItem ,
11- // Popup,
12- // showPopup,
13- TextItem
11+ Popup ,
12+ showPopup ,
13+ TextItem ,
14+ GroupItem
1415} from '@jupyterlab/statusbar' ;
1516
17+ import { DefaultIconReact } from '@jupyterlab/ui-components' ;
1618import { JupyterLabWidgetAdapter } from '../jl_adapter' ;
19+ import { VirtualDocument } from '../../../virtual/document' ;
20+ import { LSPConnection } from '../../../connection' ;
21+
22+ class LSPPopup extends VDomRenderer < LSPStatus . Model > {
23+ constructor ( model : LSPStatus . Model ) {
24+ super ( ) ;
25+ this . model = model ;
26+ // TODO: add proper, custom class?
27+ this . addClass ( 'p-Menu' ) ;
28+ }
29+ render ( ) {
30+ if ( ! this . model ) {
31+ return null ;
32+ }
33+ return (
34+ < GroupItem spacing = { 4 } className = { 'p-Menu-item' } >
35+ < TextItem source = { this . model . lsp_servers } />
36+ < TextItem source = { this . model . long_message } />
37+ </ GroupItem >
38+ ) ;
39+ }
40+ }
1741
1842/**
1943 * StatusBar item.
2044 */
2145export class LSPStatus extends VDomRenderer < LSPStatus . Model > {
46+ protected _popup : Popup = null ;
2247 /**
2348 * Construct a new VDomRenderer for the status item.
2449 */
@@ -36,81 +61,216 @@ export class LSPStatus extends VDomRenderer<LSPStatus.Model> {
3661 if ( ! this . model ) {
3762 return null ;
3863 }
39- return < TextItem source = { this . model . message } onClick = { this . handleClick } /> ;
64+ return (
65+ < GroupItem
66+ spacing = { 4 }
67+ title = { 'LSP Code Intelligence' }
68+ onClick = { this . handleClick }
69+ >
70+ < DefaultIconReact name = { 'file' } top = { '2px' } kind = { 'statusBar' } />
71+ < TextItem source = { this . model . lsp_servers_truncated } />
72+ < DefaultIconReact
73+ name = { this . model . status_icon }
74+ top = { '2px' }
75+ kind = { 'statusBar' }
76+ />
77+ < TextItem source = { this . model . short_message } />
78+ </ GroupItem >
79+ ) ;
4080 }
4181
42- handleClick ( ) {
43- console . log ( 'Click;' ) ;
82+ handleClick = ( ) => {
83+ if ( this . _popup ) {
84+ this . _popup . dispose ( ) ;
85+ }
86+ this . _popup = showPopup ( {
87+ body : new LSPPopup ( this . model ) ,
88+ anchor : this ,
89+ align : 'left'
90+ } ) ;
91+ } ;
92+ }
93+
94+ type StatusCode = 'waiting' | 'initializing' | 'initialized' | 'connecting' ;
95+
96+ export interface IStatus {
97+ connected_documents : Set < VirtualDocument > ;
98+ initialized_documents : Set < VirtualDocument > ;
99+ open_connections : Array < LSPConnection > ;
100+ detected_documents : Set < VirtualDocument > ;
101+ status : StatusCode ;
102+ }
103+
104+ function collect_languages ( virtual_document : VirtualDocument ) : Set < string > {
105+ let collected = new Set < string > ( ) ;
106+ collected . add ( virtual_document . language ) ;
107+ for ( let foreign of virtual_document . foreign_documents . values ( ) ) {
108+ let foreign_languages = collect_languages ( foreign ) ;
109+ foreign_languages . forEach ( collected . add , collected ) ;
44110 }
111+ return collected ;
45112}
46113
47114export namespace LSPStatus {
48115 /**
49116 * A VDomModel for the LSP of current file editor/notebook.
50117 */
51118 export class Model extends VDomModel {
52- get message ( ) : string {
53- return (
54- 'LSP Code Intelligence: ' +
55- this . status +
56- ( this . _message ? this . _message : '' )
57- ) ;
119+ get lsp_servers ( ) : string {
120+ if ( ! this . adapter ) {
121+ return '' ;
122+ }
123+ let document = this . adapter . virtual_editor . virtual_document ;
124+ return `Languages detected: ${ [ ...collect_languages ( document ) ] . join (
125+ ', '
126+ ) } `;
127+ }
128+
129+ get lsp_servers_truncated ( ) : string {
130+ if ( ! this . adapter ) {
131+ return '' ;
132+ }
133+ let document = this . adapter . virtual_editor . virtual_document ;
134+ let foreign_languages = collect_languages ( document ) ;
135+ foreign_languages . delete ( this . adapter . language ) ;
136+ if ( foreign_languages . size ) {
137+ if ( foreign_languages . size < 4 ) {
138+ return `${ this . adapter . language } , ${ [ ...foreign_languages ] . join (
139+ ', '
140+ ) } `;
141+ }
142+ return `${ this . adapter . language } (+${ foreign_languages . size } more)` ;
143+ }
144+ return this . adapter . language ;
145+ }
146+
147+ get status ( ) : IStatus {
148+ let connection_manager = this . adapter . connection_manager ;
149+ const detected_documents = connection_manager . documents ;
150+ let connected_documents = new Set < VirtualDocument > ( ) ;
151+ let initialized_documents = new Set < VirtualDocument > ( ) ;
152+
153+ detected_documents . forEach ( ( document , id_path ) => {
154+ let connection = connection_manager . connections . get ( id_path ) ;
155+ if ( ! connection ) {
156+ return ;
157+ }
158+
159+ if ( connection . isConnected ) {
160+ connected_documents . add ( document ) ;
161+ }
162+ if ( connection . isInitialized ) {
163+ initialized_documents . add ( document ) ;
164+ }
165+ } ) ;
166+
167+ // there may be more open connections than documents if a document was recently closed
168+ // and the grace period has not passed yet
169+ let open_connections = new Array < LSPConnection > ( ) ;
170+ connection_manager . connections . forEach ( ( connection , path ) => {
171+ if ( connection . isConnected ) {
172+ open_connections . push ( connection ) ;
173+ }
174+ } ) ;
175+
176+ let status : StatusCode ;
177+ if ( detected_documents . size === 0 ) {
178+ status = 'waiting' ;
179+ // TODO: instead of detected documents, I should use "detected_documents_with_LSP_servers_available"
180+ } else if ( initialized_documents . size === detected_documents . size ) {
181+ status = 'initialized' ;
182+ } else if ( connected_documents . size === detected_documents . size ) {
183+ status = 'initializing' ;
184+ } else {
185+ status = 'connecting' ;
186+ }
187+
188+ return {
189+ open_connections,
190+ connected_documents,
191+ initialized_documents,
192+ detected_documents : new Set ( [ ...detected_documents . values ( ) ] ) ,
193+ status
194+ } ;
58195 }
59196
60- get status ( ) : string {
197+ get status_icon ( ) : string {
198+ if ( ! this . adapter ) {
199+ return 'stop' ;
200+ }
201+ let status = this . status ;
202+
203+ // TODO: associative array instead
204+ if ( status . status === 'waiting' ) {
205+ return 'refresh' ;
206+ } else if ( status . status === 'initialized' ) {
207+ return 'running' ;
208+ } else if ( status . status === 'initializing' ) {
209+ return 'refresh' ;
210+ } else if ( status . status === 'connecting' ) {
211+ return 'refresh' ;
212+ }
213+ }
214+
215+ get short_message ( ) : string {
61216 if ( ! this . adapter ) {
62217 return 'not initialized' ;
218+ }
219+ let status = this . status ;
220+
221+ let msg = '' ;
222+ // TODO: associative array instead
223+ if ( status . status === 'waiting' ) {
224+ msg = 'Waiting...' ;
225+ } else if ( status . status === 'initialized' ) {
226+ msg = `Fully initialized` ;
227+ } else if ( status . status === 'initializing' ) {
228+ msg = `Fully connected & partially initialized` ;
63229 } else {
64- let connection_manager = this . adapter . connection_manager ;
65- const documents = connection_manager . documents ;
66- let connected_documents = 0 ;
67- let initialized_documents = 0 ;
68-
69- documents . forEach ( ( document , id_path ) => {
70- let connection = connection_manager . connections . get ( id_path ) ;
71- if ( ! connection ) {
72- return ;
73- }
74-
75- // @ts -ignore
76- if ( connection . isConnected ) {
77- connected_documents += 1 ;
78- }
79- // @ts -ignore
80- if ( connection . isInitialized ) {
81- initialized_documents += 1 ;
82- }
83- } ) ;
84-
85- // there may be more open connections than documents if a document was recently closed
86- // and the grace period has not passed yet
87- let open_connections = 0 ;
88- connection_manager . connections . forEach ( ( connection , path ) => {
89- // @ts -ignore
90- if ( connection . isConnected ) {
91- open_connections += 1 ;
92- console . warn ( 'Connected:' , path ) ;
93- } else {
94- console . warn ( path ) ;
95- }
96- } ) ;
97- let msg = '' ;
98- const plural = documents . size > 1 ? 's' : '' ;
99- if ( documents . size === 0 ) {
100- msg = 'Waiting for documents initialization...' ;
101- } else if ( initialized_documents === documents . size ) {
102- msg = `Fully connected & initialized (${ documents . size } virtual document${ plural } )` ;
103- } else if ( connected_documents === documents . size ) {
104- const uninitialized = documents . size - initialized_documents ;
105- // servers for n documents did not respond ot the initialization request
106- msg = `Fully connected, but ${ uninitialized } /${ documents . size } virtual document${ plural } stuck uninitialized` ;
107- } else if ( open_connections === 0 ) {
108- msg = `No open connections (${ documents . size } virtual document${ plural } )` ;
109- } else {
110- msg = `${ connected_documents } /${ documents . size } virtual document${ plural } connected (${ open_connections } )` ;
230+ msg = `Connecting...` ;
231+ }
232+ return msg ;
233+ }
234+
235+ get long_message ( ) : string {
236+ if ( ! this . adapter ) {
237+ return 'not initialized' ;
238+ }
239+ let status = this . status ;
240+ let msg = '' ;
241+ const plural = status . detected_documents . size > 1 ? 's' : '' ;
242+ if ( status . status === 'waiting' ) {
243+ msg = 'Waiting for documents initialization...' ;
244+ } else if ( status . status === 'initialized' ) {
245+ msg = `Fully connected & initialized (${ status . detected_documents . size } virtual document${ plural } )` ;
246+ } else if ( status . status === 'initializing' ) {
247+ const uninitialized = new Set < VirtualDocument > (
248+ status . detected_documents
249+ ) ;
250+ for ( let initialized of status . initialized_documents . values ( ) ) {
251+ uninitialized . delete ( initialized ) ;
111252 }
112- return `${ this . adapter . language } | ${ msg } ` ;
253+ // servers for n documents did not respond ot the initialization request
254+ msg = `Fully connected, but ${ uninitialized . size } /${
255+ status . detected_documents . size
256+ } virtual document${ plural } stuck uninitialized: ${ [ ...uninitialized ]
257+ . map ( document => document . id_path )
258+ . join ( ', ' ) } `;
259+ } else {
260+ const unconnected = new Set < VirtualDocument > ( status . detected_documents ) ;
261+ for ( let connected of status . connected_documents . values ( ) ) {
262+ unconnected . delete ( connected ) ;
263+ }
264+
265+ msg = `${ status . connected_documents . size } /${
266+ status . detected_documents . size
267+ } virtual document${ plural } connected (${
268+ status . open_connections . length
269+ } connections; waiting for: ${ [ ...unconnected ]
270+ . map ( document => document . id_path )
271+ . join ( ', ' ) } )`;
113272 }
273+ return msg ;
114274 }
115275
116276 get adapter ( ) : JupyterLabWidgetAdapter | null {
@@ -141,7 +301,6 @@ export namespace LSPStatus {
141301 this . stateChanged . emit ( void 0 ) ;
142302 }
143303
144- private _message : string = '' ;
145304 private _adapter : JupyterLabWidgetAdapter | null = null ;
146305 }
147306}
0 commit comments