1use std::{
2 ops::{Deref, DerefMut},
3 path::PathBuf,
4 process::{Command, Stdio},
5};
6
7use pepper::{
8 buffer_position::BufferRange,
9 editor::EditorContext,
10 editor_utils::{hash_bytes, parse_process_command, LogKind, Logger},
11 events::{EditorEvent, EditorEventIter},
12 glob::{Glob, InvalidGlobError},
13 platform::{Platform, PlatformProcessHandle, PlatformRequest, ProcessTag},
14 plugin::{CompletionContext, Plugin, PluginDefinition, PluginHandle},
15 ResourceFile,
16};
17
18mod capabilities;
19mod client;
20mod client_event_handler;
21mod command;
22mod json;
23mod mode;
24mod protocol;
25
26use client::{util, Client, ClientHandle};
27use json::{JsonObject, JsonValue};
28use protocol::{ProtocolError, ResponseError, ServerEvent};
29
30const SERVER_PROCESS_BUFFER_LEN: usize = 4 * 1024;
31
32pub static DEFAULT_CONFIGS: ResourceFile = ResourceFile {
33 name: "lsp_default_configs.pepper",
34 content: include_str!("../rc/default_configs.pepper"),
35};
36
37pub static DEFINITION: PluginDefinition = PluginDefinition {
38 instantiate: |handle, ctx| {
39 command::register_commands(&mut ctx.editor.commands, handle);
40 Some(Plugin {
41 data: Box::new(LspPlugin::default()),
42
43 on_editor_events,
44
45 on_process_spawned,
46 on_process_output,
47 on_process_exit,
48
49 on_completion,
50
51 ..Default::default()
52 })
53 },
54 help_pages: &[ResourceFile {
55 name: "lsp_help.md",
56 content: include_str!("../rc/help.md"),
57 }],
58};
59
60struct ClientRecipe {
61 glob_hash: u64,
62 glob: Glob,
63 command: String,
64 root: PathBuf,
65 running_client: Option<ClientHandle>,
66}
67
68enum ClientEntry {
69 Occupied(Box<Client>),
70 Reserved,
71 Vacant,
72}
73impl ClientEntry {
74 pub fn reserve_and_take(&mut self) -> Option<Box<Client>> {
75 match self {
76 Self::Occupied(_) => {
77 let mut client = ClientEntry::Reserved;
78 std::mem::swap(self, &mut client);
79 match client {
80 Self::Occupied(client) => Some(client),
81 _ => unreachable!(),
82 }
83 }
84 _ => None,
85 }
86 }
87}
88
89pub(crate) struct ClientGuard(Box<Client>);
90impl Deref for ClientGuard {
91 type Target = Client;
92 fn deref(&self) -> &Self::Target {
93 self.0.deref()
94 }
95}
96impl DerefMut for ClientGuard {
97 fn deref_mut(&mut self) -> &mut Self::Target {
98 self.0.deref_mut()
99 }
100}
101impl Drop for ClientGuard {
102 fn drop(&mut self) {
103 panic!("forgot to call 'release' on LspPlugin with ClientGuard");
104 }
105}
106
107#[derive(Default)]
108pub(crate) struct LspPlugin {
109 entries: Vec<ClientEntry>,
110 recipes: Vec<ClientRecipe>,
111 current_client_handle: Option<ClientHandle>,
112}
113
114impl LspPlugin {
115 pub fn add_recipe(
116 &mut self,
117 glob: &str,
118 command: &str,
119 root: Option<&str>,
120 ) -> Result<(), InvalidGlobError> {
121 let glob_hash = hash_bytes(glob.as_bytes());
122 for recipe in &mut self.recipes {
123 if recipe.glob_hash == glob_hash {
124 recipe.command.clear();
125 recipe.command.push_str(command);
126 recipe.root.clear();
127 if let Some(path) = root {
128 recipe.root.push(path);
129 }
130 recipe.running_client = None;
131 return Ok(());
132 }
133 }
134
135 let mut recipe_glob = Glob::default();
136 recipe_glob.compile(glob)?;
137 self.recipes.push(ClientRecipe {
138 glob_hash,
139 glob: recipe_glob,
140 command: command.into(),
141 root: root.unwrap_or("").into(),
142 running_client: None,
143 });
144 Ok(())
145 }
146
147 pub fn start(
148 &mut self,
149 platform: &mut Platform,
150 plugin_handle: PluginHandle,
151 mut command: Command,
152 root: PathBuf,
153 ) -> ClientHandle {
154 fn find_vacant_entry(lsp: &mut LspPlugin) -> ClientHandle {
155 for (i, entry) in lsp.entries.iter_mut().enumerate() {
156 if let ClientEntry::Vacant = entry {
157 return ClientHandle(i as _);
158 }
159 }
160 let handle = ClientHandle(lsp.entries.len() as _);
161 lsp.entries.push(ClientEntry::Vacant);
162 handle
163 }
164
165 let handle = find_vacant_entry(self);
166
167 command
168 .stdin(Stdio::piped())
169 .stdout(Stdio::piped())
170 .stderr(Stdio::null());
171
172 platform.requests.enqueue(PlatformRequest::SpawnProcess {
173 tag: ProcessTag::Plugin {
174 plugin_handle,
175 id: handle.0 as _,
176 },
177 command,
178 buf_len: SERVER_PROCESS_BUFFER_LEN,
179 });
180
181 let client = Client::new(handle, root);
182 self.entries[handle.0 as usize] = ClientEntry::Occupied(Box::new(client));
183 handle
184 }
185
186 pub fn stop(
187 &mut self,
188 platform: &mut Platform,
189 handle: ClientHandle,
190 logger: &mut Logger,
191 ) -> bool {
192 match &mut self.entries[handle.0 as usize] {
193 ClientEntry::Occupied(client) => {
194 let _ = client.notify(platform, "exit", JsonObject::default(), logger);
195 if let Some(process_handle) = client.protocol.process_handle() {
196 platform.requests.enqueue(PlatformRequest::KillProcess {
197 handle: process_handle,
198 });
199 }
200
201 self.entries[handle.0 as usize] = ClientEntry::Vacant;
202 for recipe in &mut self.recipes {
203 if recipe.running_client == Some(handle) {
204 recipe.running_client = None;
205 }
206 }
207
208 true
209 }
210 _ => false,
211 }
212 }
213
214 pub fn stop_all(&mut self, platform: &mut Platform, logger: &mut Logger) -> bool {
215 let mut any_stopped = false;
216 for i in 0..self.entries.len() {
217 any_stopped = any_stopped || self.stop(platform, ClientHandle(i as _), logger);
218 }
219
220 any_stopped
221 }
222
223 pub(crate) fn get_mut(&mut self, handle: ClientHandle) -> Option<&mut Client> {
224 match &mut self.entries[handle.0 as usize] {
225 ClientEntry::Occupied(client) => Some(client.deref_mut()),
226 _ => None,
227 }
228 }
229
230 pub(crate) fn acquire(&mut self, handle: ClientHandle) -> Option<ClientGuard> {
231 self.entries[handle.0 as usize]
232 .reserve_and_take()
233 .map(ClientGuard)
234 }
235
236 pub(crate) fn release(&mut self, mut guard: ClientGuard) {
237 let index = guard.handle().0 as usize;
238 let raw = guard.deref_mut() as *mut _;
239 std::mem::forget(guard);
240 let client = unsafe { Box::from_raw(raw) };
241 self.entries[index] = ClientEntry::Occupied(client);
242 }
243
244 pub(crate) fn find_client<P>(&mut self, mut predicate: P) -> Option<ClientGuard>
245 where
246 P: FnMut(&Client) -> bool,
247 {
248 for entry in &mut self.entries {
249 if let ClientEntry::Occupied(c) = entry {
250 if predicate(c) {
251 let client = entry.reserve_and_take().unwrap();
252 return Some(ClientGuard(client));
253 }
254 }
255 }
256
257 None
258 }
259}
260
261fn on_editor_events(plugin_handle: PluginHandle, ctx: &mut EditorContext) {
262 let lsp = ctx.plugins.get_as::<LspPlugin>(plugin_handle);
263
264 let mut events = EditorEventIter::new();
265 while let Some(event) = events.next(ctx.editor.events.reader()) {
266 if let EditorEvent::BufferRead { handle } = *event {
267 let buffer_path = match ctx.editor.buffers.get(handle).path.to_str() {
268 Some(path) => path,
269 None => continue,
270 };
271 let (index, recipe) = match lsp
272 .recipes
273 .iter_mut()
274 .enumerate()
275 .find(|(_, r)| r.glob.matches(buffer_path))
276 {
277 Some(recipe) => recipe,
278 None => continue,
279 };
280 if recipe.running_client.is_some() {
281 continue;
282 }
283 let command = match parse_process_command(&recipe.command) {
284 Some(command) => command,
285 None => {
286 ctx.editor
287 .logger
288 .write(LogKind::Error)
289 .fmt(format_args!("invalid lsp command '{}'", &recipe.command));
290 continue;
291 }
292 };
293
294 let root = if recipe.root.as_os_str().is_empty() {
295 ctx.editor.current_directory.clone()
296 } else {
297 recipe.root.clone()
298 };
299
300 let client_handle = lsp.start(&mut ctx.platform, plugin_handle, command, root);
301 lsp.recipes[index].running_client = Some(client_handle);
302 }
303 }
304
305 for entry in &mut lsp.entries {
306 let client = match entry {
307 ClientEntry::Occupied(client) => client,
308 _ => continue,
309 };
310 if !client.initialized {
311 continue;
312 }
313
314 let mut events = EditorEventIter::new();
315 while let Some(event) = events.next(ctx.editor.events.reader()) {
316 client.json.clear();
317
318 match *event {
319 EditorEvent::Idle => {
320 util::send_pending_did_change(client, &mut ctx.editor, &mut ctx.platform);
321 }
322 EditorEvent::BufferTextInserts { handle, inserts } => {
323 let buffer = ctx.editor.buffers.get(handle);
324 if buffer.path.to_str() != ctx.editor.logger.log_file_path() {
325 for insert in inserts.as_slice(ctx.editor.events.reader()) {
326 let text = insert.text(ctx.editor.events.reader());
327 let range = BufferRange::between(insert.range.from, insert.range.from);
328 client.versioned_buffers.add_edit(handle, range, text);
329 }
330 }
331 }
332 EditorEvent::BufferRangeDeletes { handle, deletes } => {
333 let buffer = ctx.editor.buffers.get(handle);
334 if buffer.path.to_str() != ctx.editor.logger.log_file_path() {
335 for &range in deletes.as_slice(ctx.editor.events.reader()) {
336 client.versioned_buffers.add_edit(handle, range, "");
337 }
338 }
339 }
340 EditorEvent::BufferRead { handle } => {
341 let buffer = ctx.editor.buffers.get(handle);
342 if buffer.path.to_str() != ctx.editor.logger.log_file_path() {
343 client.versioned_buffers.dispose(handle);
344 util::send_did_open(
345 client,
346 &ctx.editor.buffers,
347 &mut ctx.platform,
348 handle,
349 &mut ctx.editor.logger,
350 );
351 }
352 }
353 EditorEvent::BufferWrite { handle, .. } => {
354 let buffer = ctx.editor.buffers.get(handle);
355 if buffer.path.to_str() != ctx.editor.logger.log_file_path() {
356 util::send_pending_did_change(client, &mut ctx.editor, &mut ctx.platform);
357 util::send_did_save(client, &mut ctx.editor, &mut ctx.platform, handle);
358 }
359 }
360 EditorEvent::BufferClose { handle } => {
361 let buffer = ctx.editor.buffers.get(handle);
362 if buffer.path.to_str() != ctx.editor.logger.log_file_path() {
363 client.versioned_buffers.dispose(handle);
364 client.diagnostics.on_close_buffer(handle);
365 util::send_pending_did_change(client, &mut ctx.editor, &mut ctx.platform);
366 util::send_did_close(client, &mut ctx.editor, &mut ctx.platform, handle);
367 }
368 }
369 EditorEvent::FixCursors { .. } => (),
370 EditorEvent::BufferBreakpointsChanged { .. } => (),
371 }
372 }
373 }
374}
375
376fn on_process_spawned(
377 plugin_handle: PluginHandle,
378 ctx: &mut EditorContext,
379 client_index: u32,
380 process_handle: PlatformProcessHandle,
381) {
382 if let ClientEntry::Occupied(client) =
383 &mut ctx.plugins.get_as::<LspPlugin>(plugin_handle).entries[client_index as usize]
384 {
385 client.protocol.set_process_handle(process_handle);
386 client.json.clear();
387 client.initialize(&mut ctx.platform, &mut ctx.editor.logger);
388 }
389}
390
391fn on_process_output(
392 plugin_handle: PluginHandle,
393 ctx: &mut EditorContext,
394 client_index: u32,
395 bytes: &[u8],
396) {
397 let lsp = ctx.plugins.get_as::<LspPlugin>(plugin_handle);
398 let mut client_guard = match lsp.acquire(ClientHandle(client_index as _)) {
399 Some(client) => client,
400 None => return,
401 };
402 let client = client_guard.deref_mut();
403 client.json.clear();
404
405 let mut events = client.protocol.parse_events(bytes);
406 while let Some(event) = events.next(&mut client.protocol, &mut client.json) {
407 match event {
408 ServerEvent::ParseError => {
409 {
410 let mut log_writer = ctx.editor.logger.write(LogKind::Diagnostic);
411 log_writer.str("lsp: ");
412 log_writer.str("send parse error\nrequest_id: ");
413 let _ = client.json.write(&mut log_writer, &JsonValue::Null);
414 }
415
416 client.respond(
417 &mut ctx.platform,
418 JsonValue::Null,
419 Err(ResponseError::parse_error()),
420 &mut ctx.editor.logger,
421 );
422 }
423 ServerEvent::Request(request) => {
424 let request_id = request.id.clone();
425 match client_event_handler::on_request(client, ctx, request) {
426 Ok(value) => client.respond(
427 &mut ctx.platform,
428 request_id,
429 Ok(value),
430 &mut ctx.editor.logger,
431 ),
432 Err(ProtocolError::ParseError) => {
433 client.respond(
434 &mut ctx.platform,
435 request_id,
436 Err(ResponseError::parse_error()),
437 &mut ctx.editor.logger,
438 );
439 }
440 Err(ProtocolError::MethodNotFound) => {
441 client.respond(
442 &mut ctx.platform,
443 request_id,
444 Err(ResponseError::method_not_found()),
445 &mut ctx.editor.logger,
446 );
447 }
448 }
449 }
450 ServerEvent::Notification(notification) => {
451 let result =
452 client_event_handler::on_notification(client, ctx, plugin_handle, notification);
453 if let Err(error) = result {
454 ctx.editor
455 .logger
456 .write(LogKind::Error)
457 .fmt(format_args!("lsp protocol error: {}", error));
458 }
459 }
460 ServerEvent::Response(response) => {
461 let result =
462 client_event_handler::on_response(client, ctx, plugin_handle, response);
463 if let Err(error) = result {
464 ctx.editor
465 .logger
466 .write(LogKind::Error)
467 .fmt(format_args!("lsp protocol error: {}", error));
468 }
469 }
470 }
471 }
472 events.finish(&mut client.protocol);
473
474 let lsp = ctx.plugins.get_as::<LspPlugin>(plugin_handle);
475 lsp.release(client_guard);
476}
477
478fn on_process_exit(plugin_handle: PluginHandle, ctx: &mut EditorContext, client_index: u32) {
479 for buffer in ctx.editor.buffers.iter_mut() {
480 let mut lints = buffer.lints.mut_guard(plugin_handle);
481 lints.clear();
482 }
483
484 let lsp = ctx.plugins.get_as::<LspPlugin>(plugin_handle);
485 if let ClientEntry::Occupied(client) = &mut lsp.entries[client_index as usize] {
486 {
487 let mut log_writer = ctx.editor.logger.write(LogKind::Diagnostic);
488 log_writer.str("lsp: ");
489 log_writer.str("lsp server stopped");
490 }
491
492 let client_handle = client.handle();
493 for recipe in &mut lsp.recipes {
494 if recipe.running_client == Some(client_handle) {
495 recipe.running_client = None;
496 }
497 }
498 }
499}
500
501fn on_completion(
502 handle: PluginHandle,
503 ctx: &mut EditorContext,
504 completion_ctx: &CompletionContext,
505) -> bool {
506 let lsp = ctx.plugins.get_as::<LspPlugin>(handle);
507 for entry in &mut lsp.entries {
508 let client = match entry {
509 ClientEntry::Occupied(client) => client,
510 _ => continue,
511 };
512 client.json.clear();
513
514 let mut should_complete = completion_ctx.completion_requested;
515
516 if !should_complete {
517 if let Some(c) = ctx
518 .editor
519 .buffers
520 .get(completion_ctx.buffer_handle)
521 .content()
522 .text_range(completion_ctx.word_range)
523 .next()
524 .and_then(|s| s.chars().next_back())
525 {
526 if client.signature_help_triggers().contains(c) {
527 client.signature_help(
528 &mut ctx.editor,
529 &mut ctx.platform,
530 completion_ctx.buffer_handle,
531 completion_ctx.cursor_position,
532 );
533 return false;
534 }
535
536 should_complete = client.completion_triggers().contains(c);
537 }
538 }
539
540 if should_complete {
541 client.completion(
542 &mut ctx.editor,
543 &mut ctx.platform,
544 completion_ctx.client_handle,
545 completion_ctx.buffer_handle,
546 completion_ctx.cursor_position,
547 );
548 return true;
549 }
550 }
551
552 false
553}