1+ class LSPClient {
2+ constructor ( { serverUrl, editor, documentUri, language } ) {
3+ this . editor = editor ;
4+ this . documentUri = documentUri ;
5+ this . serverUrl = serverUrl ;
6+ this . ws = null ;
7+ this . messageId = 0 ;
8+ this . pendingRequests = new Map ( ) ;
9+ this . documentVersion = 1 ;
10+ this . currentLanguage = language ;
11+ }
12+
13+
14+ // Establish WebSocket connection and initialize LSP
15+ connect ( ) {
16+ this . ws = new WebSocket ( this . serverUrl ) ;
17+ this . ws . onopen = ( ) => {
18+ this . initializeLSP ( ) ;
19+ } ;
20+ this . ws . onmessage = ( event ) => this . handleMessage ( event ) ;
21+ this . ws . onerror = ( error ) => console . error ( 'WebSocket error:' , error ) ;
22+ this . ws . onclose = ( ) => console . log ( 'WebSocket closed' ) ;
23+
24+ // Listen to editor changes
25+ this . editor . getSession ( ) . on ( 'change' , ( delta ) => {
26+ this . sendDidChange ( ) ;
27+ } ) ;
28+
29+ // Add LSP completer for autocompletion
30+ this . editor . completers = this . editor . completers || [ ] ;
31+ this . editor . completers . push ( {
32+ getCompletions : ( editor , session , pos , prefix , callback ) => {
33+ this . requestCompletions ( pos , prefix , callback ) ;
34+ }
35+ } ) ;
36+ }
37+
38+ // Disconnect from the LSP server
39+ disconnect ( ) {
40+ if ( this . ws ) {
41+ this . ws . close ( ) ;
42+ }
43+ }
44+
45+ // Send initialize request to LSP server
46+ initializeLSP ( ) {
47+ const initParams = {
48+ processId : null ,
49+ clientInfo : { name : 'ace-lsp-client' } ,
50+ capabilities : {
51+ textDocument : {
52+ completion : { dynamicRegistration : false } ,
53+ publishDiagnostics : { relatedInformation : true }
54+ }
55+ }
56+ } ;
57+ this . sendRequest ( 'initialize' , initParams ) . then ( ( result ) => {
58+ this . sendNotification ( 'initialized' , { } ) ;
59+ this . sendDidOpen ( ) ;
60+ } ) . catch ( ( error ) => console . error ( 'Initialization failed:' , error ) ) ;
61+ }
62+
63+ // Send textDocument/didOpen notification
64+ sendDidOpen ( ) {
65+ const params = {
66+ textDocument : {
67+ uri : this . documentUri ,
68+ languageId : this . currentLanguage ,
69+ version : this . documentVersion ,
70+ text : this . editor . getValue ( )
71+ }
72+ } ;
73+ this . sendNotification ( 'textDocument/didOpen' , params ) ;
74+ }
75+
76+ // Send textDocument/didChange notification
77+ sendDidChange ( ) {
78+ const params = {
79+ textDocument : {
80+ uri : this . documentUri ,
81+ version : ++ this . documentVersion
82+ } ,
83+ contentChanges : [ { text : this . editor . getValue ( ) } ]
84+ } ;
85+ this . sendNotification ( 'textDocument/didChange' , params ) ;
86+ }
87+
88+ // Request completions from LSP server
89+ requestCompletions ( position , prefix , callback ) {
90+ const params = {
91+ textDocument : { uri : this . documentUri } ,
92+ position : { line : position . row , character : position . column }
93+ } ;
94+ this . sendRequest ( 'textDocument/completion' , params ) . then ( ( result ) => {
95+ const completions = ( result ?. items || [ ] ) . map ( item => ( {
96+ caption : item . label ,
97+ value : item . insertText || item . label ,
98+ meta : item . detail || 'completion'
99+ } ) ) ;
100+ callback ( null , completions ) ;
101+ } ) . catch ( ( error ) => {
102+ console . error ( 'Completion failed:' , error ) ;
103+ callback ( null , [ ] ) ;
104+ } ) ;
105+ }
106+
107+ // Send a request and return a promise for the response
108+ sendRequest ( method , params ) {
109+ return new Promise ( ( resolve , reject ) => {
110+ const id = ++ this . messageId ;
111+ const message = { jsonrpc : '2.0' , id, method, params } ;
112+ this . pendingRequests . set ( id , { resolve, reject } ) ;
113+ this . ws . send ( JSON . stringify ( message ) ) ;
114+ } ) ;
115+ }
116+
117+ // Send a notification (no response expected)
118+ sendNotification ( method , params ) {
119+ const message = { jsonrpc : '2.0' , method, params } ;
120+ this . ws . send ( JSON . stringify ( message ) ) ;
121+ }
122+
123+ // Handle incoming WebSocket messages
124+ handleMessage ( event ) {
125+ const message = JSON . parse ( event . data ) ;
126+ if ( message . id && this . pendingRequests . has ( message . id ) ) {
127+ const { resolve, reject } = this . pendingRequests . get ( message . id ) ;
128+ if ( message . error ) {
129+ reject ( message . error ) ;
130+ } else {
131+ resolve ( message . result ) ;
132+ }
133+ this . pendingRequests . delete ( message . id ) ;
134+ } else if ( message . method === 'textDocument/publishDiagnostics' ) {
135+ this . handleDiagnostics ( message . params ) ;
136+ }
137+ }
138+
139+ // Handle diagnostics from LSP server and display in editor
140+ handleDiagnostics ( params ) {
141+ const diagnostics = params . diagnostics || [ ] ;
142+ const annotations = diagnostics . map ( d => ( {
143+ row : d . range . start . line ,
144+ column : d . range . start . character ,
145+ text : d . message ,
146+ type : d . severity === 1 ? 'error' : 'warning'
147+ } ) ) ;
148+ this . editor . getSession ( ) . setAnnotations ( annotations ) ;
149+
150+ // Optional: Update diagnostics list in HTML (assumes element exists)
151+ const diagnosticsList = document . getElementById ( 'diagnosticsList' ) ;
152+ if ( diagnosticsList ) {
153+ diagnosticsList . innerHTML = diagnostics . map ( d =>
154+ `<li>${ d . message } at line ${ d . range . start . line + 1 } </li>`
155+ ) . join ( '' ) ;
156+ }
157+ }
158+ }
0 commit comments