Skip to content

Commit 82fb53f

Browse files
lspClient
1 parent c7e8e07 commit 82fb53f

File tree

2 files changed

+158
-428
lines changed

2 files changed

+158
-428
lines changed

src/lib/LSPClient.js

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
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

Comments
 (0)