mojom_lsp/server/
server.rs

1// Copyright 2020 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::io::{BufReader, BufWriter, Read, Write};
16use std::path::PathBuf;
17
18use serde_json::Value;
19
20use super::protocol::{
21    read_message, ErrorCodes, Message, NotificationMessage, RequestMessage, ResponseError,
22};
23
24use super::diagnostic::{start_diagnostics_thread, DiagnosticsThread};
25use super::messagesender::{start_message_sender_thread, MessageSender};
26
27#[derive(PartialEq)]
28enum State {
29    Initialized,
30    ShuttingDown,
31}
32
33struct ServerContext {
34    state: State,
35    // A handler to send messages on the main thread.
36    msg_sender: MessageSender,
37    // A handler to the diagnostics thread.
38    diag: DiagnosticsThread,
39    // Set when `exit` notification is received.
40    exit_code: Option<i32>,
41}
42
43impl ServerContext {
44    fn new(msg_sender: MessageSender, diag: DiagnosticsThread) -> ServerContext {
45        ServerContext {
46            state: State::Initialized,
47            msg_sender: msg_sender,
48            diag: diag,
49            exit_code: None,
50        }
51    }
52}
53
54// Requests
55
56fn get_request_params<P: serde::de::DeserializeOwned>(
57    params: Value,
58) -> std::result::Result<P, ResponseError> {
59    serde_json::from_value::<P>(params)
60        .map_err(|err| ResponseError::new(ErrorCodes::InvalidRequest, err.to_string()))
61}
62
63fn handle_request(ctx: &mut ServerContext, msg: RequestMessage) -> anyhow::Result<()> {
64    let id = msg.id;
65    let method = msg.method.as_str();
66    log::debug!("[recv] Request: id = {}, method = {}", id, method);
67
68    // Workaround for Eglot. It sends "exit" as a request, not as a notification.
69    if method == "exit" {
70        exit_notification(ctx);
71        return Ok(());
72    }
73
74    use lsp_types::request::*;
75    let res = match method {
76        Initialize::METHOD => initialize_request(),
77        Shutdown::METHOD => shutdown_request(ctx),
78        GotoDefinition::METHOD => get_request_params(msg.params)
79            .and_then(|params| goto_definition_request(&mut ctx.diag, params)),
80        _ => unimplemented_request(id, method),
81    };
82    match res {
83        Ok(res) => {
84            ctx.msg_sender.send_success_response(id, res);
85        }
86        Err(err) => ctx.msg_sender.send_error_response(id, err),
87    };
88    Ok(())
89}
90
91type RequestResult = std::result::Result<Value, ResponseError>;
92
93fn unimplemented_request(id: u64, method_name: &str) -> RequestResult {
94    let msg = format!(
95        "Unimplemented request: id = {} method = {}",
96        id, method_name
97    );
98    let err = ResponseError::new(ErrorCodes::InternalError, msg);
99    Err(err)
100}
101
102fn initialize_request() -> RequestResult {
103    // The server was already initialized.
104    let error_message = "Unexpected initialize message".to_owned();
105    Err(ResponseError::new(
106        ErrorCodes::ServerNotInitialized,
107        error_message,
108    ))
109}
110
111fn shutdown_request(ctx: &mut ServerContext) -> RequestResult {
112    ctx.state = State::ShuttingDown;
113    Ok(Value::Null)
114}
115
116fn goto_definition_request(
117    diag: &mut DiagnosticsThread,
118    params: lsp_types::TextDocumentPositionParams,
119) -> RequestResult {
120    if let Some(loc) = diag.goto_definition(params.text_document.uri, params.position) {
121        let res = serde_json::to_value(loc).unwrap();
122        return Ok(res);
123    }
124    return Ok(Value::Null);
125}
126
127// Notifications
128
129fn get_params<P: serde::de::DeserializeOwned>(params: Value) -> anyhow::Result<P> {
130    serde_json::from_value::<P>(params).map_err(|err| err.into())
131}
132
133fn handle_notification(ctx: &mut ServerContext, msg: NotificationMessage) -> anyhow::Result<()> {
134    log::debug!("[recv] Notification: method = {}", msg.method);
135
136    use lsp_types::notification::*;
137    match msg.method.as_str() {
138        Exit::METHOD => exit_notification(ctx),
139        DidOpenTextDocument::METHOD => {
140            get_params(msg.params).map(|params| did_open_text_document(ctx, params))?;
141        }
142        DidChangeTextDocument::METHOD => {
143            get_params(msg.params).map(|params| did_change_text_document(ctx, params))?;
144        }
145        // Accept following notifications but do nothing.
146        DidChangeConfiguration::METHOD => (),
147        WillSaveTextDocument::METHOD => (),
148        DidSaveTextDocument::METHOD => (),
149        _ => {
150            log::warn!("Received unimplemented notification: {:#?}", msg);
151        }
152    }
153    Ok(())
154}
155
156fn exit_notification(ctx: &mut ServerContext) {
157    // https://microsoft.github.io/language-server-protocol/specification#exit
158    if ctx.state == State::ShuttingDown {
159        ctx.exit_code = Some(0);
160    } else {
161        ctx.exit_code = Some(1);
162    }
163}
164
165fn did_open_text_document(ctx: &mut ServerContext, params: lsp_types::DidOpenTextDocumentParams) {
166    ctx.diag
167        .check(params.text_document.uri, params.text_document.text);
168}
169
170fn did_change_text_document(
171    ctx: &mut ServerContext,
172    params: lsp_types::DidChangeTextDocumentParams,
173) {
174    let uri = params.text_document.uri.clone();
175    let content = params
176        .content_changes
177        .iter()
178        .map(|i| i.text.to_owned())
179        .collect::<Vec<_>>();
180    let text = content.join("");
181    ctx.diag.check(uri, text);
182}
183
184fn is_chromium_src_dir(path: &PathBuf) -> bool {
185    // The root is named `src`.
186    if !path.file_name().map(|name| name == "src").unwrap_or(false) {
187        return false;
188    }
189
190    // Check if the parent directory contains `.gclient`.
191    match path.parent() {
192        Some(parent) => parent.join(".gclient").is_file(),
193        None => false,
194    }
195}
196
197fn find_chromium_src_dir(mut path: PathBuf) -> PathBuf {
198    if is_chromium_src_dir(&path) {
199        return path;
200    }
201
202    let original = path.clone();
203    while path.pop() {
204        if is_chromium_src_dir(&path) {
205            return path;
206        }
207    }
208    original
209}
210
211fn get_root_path(params: &lsp_types::InitializeParams) -> Option<PathBuf> {
212    let uri = match params.root_uri {
213        Some(ref uri) => uri,
214        None => return None,
215    };
216    let path = match uri.to_file_path() {
217        Ok(path) => path,
218        Err(_) => return None,
219    };
220
221    // Try to find chromium's `src` directory and use it if exists.
222    let path = find_chromium_src_dir(path);
223    Some(path)
224}
225
226// Returns exit code.
227pub fn start<R, W>(reader: R, writer: W) -> anyhow::Result<i32>
228where
229    R: Read,
230    W: Write + Send + 'static,
231{
232    let mut reader = BufReader::new(reader);
233    let mut writer = BufWriter::new(writer);
234
235    let params = super::initialization::initialize(&mut reader, &mut writer)?;
236
237    let root_path = get_root_path(&params).unwrap_or(PathBuf::new());
238
239    let msg_sender_thread = start_message_sender_thread(writer);
240    let diag = start_diagnostics_thread(root_path, msg_sender_thread.get_sender());
241
242    let mut ctx = ServerContext::new(msg_sender_thread.get_sender(), diag);
243    loop {
244        let message = read_message(&mut reader)?;
245        match message {
246            Message::Request(request) => handle_request(&mut ctx, request)?,
247            Message::Notofication(notification) => handle_notification(&mut ctx, notification)?,
248            _ => unreachable!(),
249        };
250
251        if let Some(exit_code) = ctx.exit_code {
252            return Ok(exit_code);
253        }
254    }
255}
256
257#[cfg(test)]
258mod tests {
259    use super::super::protocol::{self, read_message, write_notification, write_request};
260    use super::*;
261
262    use lsp_types::notification::*;
263    use lsp_types::request::*;
264    use pipe::pipe;
265
266    #[test]
267    fn test_server_init() {
268        let (reader, mut writer) = pipe();
269
270        let capabilities = lsp_types::ClientCapabilities {
271            workspace: None,
272            text_document: None,
273            window: None,
274            general: None,
275            experimental: None,
276        };
277        // TODO: Try to remove allow(deprecated)
278        #[allow(deprecated)]
279        let params = lsp_types::InitializeParams {
280            process_id: None,
281            root_path: None,
282            root_uri: None,
283            initialization_options: None,
284            capabilities: capabilities,
285            trace: None,
286            workspace_folders: None,
287            client_info: None,
288            locale: None,
289        };
290        let params = serde_json::to_value(&params).unwrap();
291
292        let (r, w) = pipe();
293        let handle = std::thread::spawn(move || {
294            let status = start(reader, w);
295            status
296        });
297
298        write_request(
299            &mut writer,
300            1,
301            lsp_types::request::Initialize::METHOD,
302            params,
303        )
304        .unwrap();
305
306        let mut r = BufReader::new(r);
307        let msg = read_message(&mut r).unwrap();
308        match msg {
309            protocol::Message::Response(msg) => {
310                assert_eq!(1, msg.id);
311            }
312            _ => unreachable!(),
313        }
314
315        write_notification(
316            &mut writer,
317            lsp_types::notification::Initialized::METHOD,
318            serde_json::Value::Null,
319        )
320        .unwrap();
321
322        write_request(
323            &mut writer,
324            2,
325            lsp_types::request::Shutdown::METHOD,
326            serde_json::Value::Null,
327        )
328        .unwrap();
329
330        let msg = read_message(&mut r).unwrap();
331        match msg {
332            protocol::Message::Response(msg) => {
333                assert_eq!(2, msg.id);
334            }
335            _ => unreachable!(),
336        }
337
338        write_notification(
339            &mut writer,
340            lsp_types::notification::Exit::METHOD,
341            serde_json::Value::Null,
342        )
343        .unwrap();
344
345        drop(writer);
346        drop(r);
347
348        let status = handle.join().unwrap();
349        assert!(status.is_ok());
350    }
351}