Skip to main content

pepper_plugin_lsp/
lib.rs

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}