Skip to main content

a2kit/lang/
server.rs

1//! # Generics and traits for language servers
2//! 
3//! These traits can be used to aid in the handling of requests
4//! that are typically sent by a language client.  The `Analysis`
5//! trait is also used by the CLI `verify` subcommand.
6
7use std::io::Write;
8use std::str::FromStr;
9use lsp_types as lsp;
10use lsp::request::Request;
11use tree_sitter;
12use std::collections::HashMap;
13use std::sync::Arc;
14
15use crate::{STDRESULT,DYNERR};
16
17pub const TOKEN_TYPES: [&str;21] = ["comment", "string", "keyword", "number", "regexp", "operator", "namespace",
18"type", "struct", "class", "interface", "enum", "typeParameter", "function",
19"method", "decorator", "macro", "variable", "parameter", "property", "label"];
20
21// JSON-RPC error codes; are they defined somewhere else?
22// -32768 through -32000 are reserved
23mod rpc_error {
24    pub const PARSE_ERROR: i32 = -32700;
25    // pub const INVALID_REQUEST: i32 = -32600;
26    // pub const METHOD_NOT_FOUND: i32 = -32601;
27    // pub const INVALID_PARAMS: i32 = -32602;
28    // pub const INTERNAL_ERROR: i32 = -32603;
29}
30
31/// Build an object around this trait to generate hovers.  Then when the client requests
32/// hovers, feed that object into Checkpoint::hover_response.
33pub trait Hovers {
34    fn get(&mut self, line: String, row: isize, col: isize) -> Option<lsp::Hover>;
35}
36
37/// Build an object around this trait to generate completions.  Then when the client requests
38/// completions, feed that object into Checkpoint::completion_response.
39pub trait Completions {
40	fn get(&mut self,lines: &mut std::str::Lines, ctx: &lsp::CompletionContext, pos: &lsp::Position) -> Result<Vec<lsp::CompletionItem>,String>;
41}
42
43/// Build an object around this trait to generate semantic tokens.  Then when the client requests
44/// tokens, feed that object into Checkpoint::sem_tok_response.
45pub trait Tokens {
46	fn get(&mut self, txt: &str) -> Result<lsp::SemanticTokens,DYNERR>;
47}
48
49/// This important trait is used to provide data from a prior analysis to the LSP client.
50/// The implementation defines all mechanisms for updating document and symbol information.
51/// A typical pattern is to store the implementation in a map keyed by the document's URI string.
52/// The default `*_response` functions provide a convenient way to respond to a client's
53/// requests.  These functions are intended to mutate a default response within a match.
54pub trait Checkpoint {
55    /// Get a copy of the most recently checkpointed document and version.
56    fn get_doc(&self) -> super::Document;
57    /// Get a row from the most recently checkpointed document.
58    fn get_line(&self,row: usize) -> Option<String>;
59    fn get_symbols(&self) -> Vec<lsp::DocumentSymbol>;
60    fn get_decs(&self,loc: &lsp::Location) -> Vec<lsp::Location>;
61    fn get_defs(&self,loc: &lsp::Location) -> Vec<lsp::Location>;
62    fn get_refs(&self,loc: &lsp::Location) -> Vec<lsp::Location>;
63    fn get_renamables(&self,loc: &lsp::Location) -> Vec<lsp::Location>;
64    fn get_folding_ranges(&self) -> Vec<lsp::FoldingRange>;
65    fn symbol_response(chkpts: HashMap<String,Arc<&Self>>, req: lsp_server::Request, resp: &mut lsp_server::Response) {
66        if let Ok(params) = serde_json::from_value::<lsp::DocumentSymbolParams>(req.params) {
67            let uri = super::normalize_client_uri(params.text_document.uri);
68            if let Some(chkpt) = chkpts.get(&uri.to_string()) {
69                *resp = match serde_json::to_value::<Vec<lsp::DocumentSymbol>>(chkpt.get_symbols()) {
70                    Ok(result) => lsp_server::Response::new_ok(req.id,Some(result)),
71                    Err(_) => lsp_server::Response::new_err(req.id,rpc_error::PARSE_ERROR,"symbol request failed while parsing".to_string())
72                };
73            }
74        }
75    }
76    fn goto_dec_response(chkpts: HashMap<String,Arc<&Self>>, req: lsp_server::Request, resp: &mut lsp_server::Response) {
77        if let Ok(params) = serde_json::from_value::<lsp::GotoDefinitionParams>(req.params) {
78            let uri = super::normalize_client_uri(params.text_document_position_params.text_document.uri);
79            let pos = params.text_document_position_params.position;
80            let loc = lsp::Location::new(uri.clone(),lsp::Range::new(pos,pos));
81            if let Some(chkpt) = chkpts.get(&uri.to_string()) {
82                *resp = match serde_json::to_value::<Vec<lsp::Location>>(chkpt.get_decs(&loc)) {
83                    Ok(result) => lsp_server::Response::new_ok(req.id,Some(result)),
84                    Err(_) => lsp_server::Response::new_err(req.id,rpc_error::PARSE_ERROR,"goto decs failed while parsing".to_string())
85                };
86            }
87        }
88    }
89    fn goto_def_response(chkpts: HashMap<String,Arc<&Self>>, req: lsp_server::Request, resp: &mut lsp_server::Response) {
90        if let Ok(params) = serde_json::from_value::<lsp::GotoDefinitionParams>(req.params) {
91            let uri = super::normalize_client_uri(params.text_document_position_params.text_document.uri);
92            let pos = params.text_document_position_params.position;
93            let loc = lsp::Location::new(uri.clone(),lsp::Range::new(pos,pos));
94            if let Some(chkpt) = chkpts.get(&uri.to_string()) {
95                *resp = match serde_json::to_value::<Vec<lsp::Location>>(chkpt.get_defs(&loc)) {
96                    Ok(result) => lsp_server::Response::new_ok(req.id,Some(result)),
97                    Err(_) => lsp_server::Response::new_err(req.id,rpc_error::PARSE_ERROR,"goto defs failed while parsing".to_string())
98                };
99            }
100        }
101    }
102    fn goto_ref_response(chkpts: HashMap<String,Arc<&Self>>, req: lsp_server::Request, resp: &mut lsp_server::Response) {
103        if let Ok(params) = serde_json::from_value::<lsp::ReferenceParams>(req.params) {
104            let uri = super::normalize_client_uri(params.text_document_position.text_document.uri);
105            let pos = params.text_document_position.position;
106            let loc = lsp::Location::new(uri.clone(),lsp::Range::new(pos,pos));
107            if let Some(chkpt) = chkpts.get(&uri.to_string()) {
108                *resp = match serde_json::to_value::<Vec<lsp::Location>>(chkpt.get_refs(&loc)) {
109                    Ok(result) => lsp_server::Response::new_ok(req.id,Some(result)),
110                    Err(_) => lsp_server::Response::new_err(req.id,rpc_error::PARSE_ERROR,"goto refs failed while parsing".to_string())
111                };
112            }
113        }
114    }
115    fn rename_response(chkpts: HashMap<String,Arc<&Self>>, req: lsp_server::Request, resp: &mut lsp_server::Response) {
116        if let Ok(params) = serde_json::from_value::<lsp::RenameParams>(req.params) {
117            let uri = super::normalize_client_uri(params.text_document_position.text_document.uri);
118            let pos = params.text_document_position.position;
119            let sel_loc = lsp::Location::new(uri.clone(),lsp::Range::new(pos,pos));
120            if let Some(chkpt) = chkpts.get(&uri.to_string()) {
121                let mut changes: HashMap<lsp::Uri,Vec<lsp::TextEdit>> = HashMap::new();
122                let locs = chkpt.get_renamables(&sel_loc);
123                for loc in locs {
124                    let new_edit = lsp::TextEdit::new(loc.range, params.new_name.clone());
125                    match changes.get_mut(&loc.uri) {
126                        Some(edits) => edits.push(new_edit),
127                        None => {
128                            let edits = vec![new_edit];
129                            changes.insert(loc.uri,edits);
130                        }
131                    };
132                }
133                *resp = match serde_json::to_value::<lsp::WorkspaceEdit>(lsp::WorkspaceEdit::new(changes)) {
134                    Ok(result) => lsp_server::Response::new_ok(req.id,result),
135                    Err(_) => lsp_server::Response::new_err(req.id,rpc_error::PARSE_ERROR,"rename request failed while parsing".to_string())
136                };
137            }
138        }
139    }
140    fn folding_range_response(chkpts: HashMap<String,Arc<&Self>>, req: lsp_server::Request, resp: &mut lsp_server::Response) {
141        if let Ok(params) = serde_json::from_value::<lsp::FoldingRangeParams>(req.params) {
142            let uri = super::normalize_client_uri(params.text_document.uri);
143            if let Some(chkpt) = chkpts.get(&uri.to_string()) {
144                let folding_ranges = chkpt.get_folding_ranges();
145                *resp = match serde_json::to_value::<Vec<lsp::FoldingRange>>(folding_ranges) {
146                    Ok(result) => lsp_server::Response::new_ok(req.id,result),
147                    Err(_) => lsp_server::Response::new_err(req.id,rpc_error::PARSE_ERROR,"folding range request failed while parsing".to_string())
148                };
149            }
150        }
151    }
152    fn hover_response<HOV: Hovers>(chkpts: HashMap<String,Arc<&Self>>, hov: &mut HOV, req: lsp_server::Request, resp: &mut lsp_server::Response) {
153        if let Ok(params) = serde_json::from_value::<lsp::HoverParams>(req.params) {
154            let uri = super::normalize_client_uri(params.text_document_position_params.text_document.uri);
155            let pos = params.text_document_position_params.position;
156            if let Some(chkpt) = chkpts.get(&uri.to_string()) {
157                if let Some(line) = chkpt.get_line(pos.line as usize) {
158                    *resp = match hov.get(line,pos.line as isize, pos.character as isize) {
159                        Some(hover) => match serde_json::to_value::<lsp::Hover>(hover) {
160                            Ok(result) => lsp_server::Response::new_ok(req.id,result),
161                            Err(_) => lsp_server::Response::new_err(req.id,rpc_error::PARSE_ERROR,"hover request failed while parsing".to_string())
162                        },
163                        None => lsp_server::Response::new_ok(req.id,serde_json::Value::Null)
164                    };
165                }
166            }
167        }
168    }
169    fn completion_response<CMP: Completions>(chkpts: HashMap<String,Arc<&Self>>, cmp: &mut CMP, req: lsp_server::Request, resp: &mut lsp_server::Response) {
170        if let Ok(params) = serde_json::from_value::<lsp::CompletionParams>(req.params) {
171            let uri = super::normalize_client_uri(params.text_document_position.text_document.uri);
172            let pos = params.text_document_position.position;
173            if let Some(chkpt) = chkpts.get(&uri.to_string()) {
174                if let Some(ctx) = params.context {
175                    *resp = match cmp.get(&mut chkpt.get_doc().text.lines(),&ctx,&pos) {
176                        Ok(lst) => {
177                            match serde_json::to_value::<lsp::CompletionResponse>(lsp::CompletionResponse::Array(lst)) {
178                                Ok(result) => lsp_server::Response::new_ok(req.id,result),
179                                Err(_) => lsp_server::Response::new_err(req.id,rpc_error::PARSE_ERROR,"completion request failed while parsing".to_string())
180                            }
181                        },
182                        Err(s) => lsp_server::Response::new_err(req.id,rpc_error::PARSE_ERROR,s)
183                    };
184                }
185            }
186        }
187    }
188    fn sem_tok_response<TOK: Tokens>(chkpts: HashMap<String,Arc<&Self>>, tok: &mut TOK, req: lsp_server::Request, resp: &mut lsp_server::Response) {
189        if let Ok(params) = serde_json::from_value::<lsp::SemanticTokensParams>(req.params) {
190            let uri: lsp::Uri =super::normalize_client_uri(params.text_document.uri);
191            if let Some(chkpt) = chkpts.get(&uri.to_string()) {
192                let doc = chkpt.get_doc();
193                if let Ok(tok) = tok.get(&doc.text) {
194                    *resp = match serde_json::to_value::<lsp::SemanticTokensResult>(lsp::SemanticTokensResult::Tokens(tok)) {
195                        Ok(result) => lsp_server::Response::new_ok(req.id,Some(result)),
196                        Err(_) => lsp_server::Response::new_err(req.id,rpc_error::PARSE_ERROR,"semantic tokens failed while parsing".to_string())
197                    };
198                }
199            }
200        }
201    }
202}
203
204/// This trait object can serve either an ordinary LSP client,
205/// or the `verify` subcommand, whether it is run from the
206/// console or in a subprocess.  For the LSP wrap this in Arc<Mutex<>>
207/// so the analysis can run in a parallel thread.
208pub trait Analysis {
209    /// Analyze source directories and volatile documents that define the workspace.
210    /// This should gather workspace level symbols and define any relationships
211    /// that may exist between files.
212    fn init_workspace(&mut self,_source_dirs: Vec<lsp::Uri>,_volatile_docs: Vec<super::Document>) -> STDRESULT {
213        Ok(())
214    }
215    /// Analyze a master document to produce diagnostic and symbol information.
216    fn analyze(&mut self,doc: &super::Document) -> STDRESULT;
217    /// Parse the JSON to update the configuration.
218    /// Unknown keys or unexpected values can be handled as the anlayzer chooses.
219    /// This tends to be used for the CLI rather than the language server.
220    fn update_config(&mut self,json_str: &str) -> STDRESULT;
221    /// Get a clone of the diagnostics for the given document.
222    /// The available documents are the master that was analyzed, or
223    /// any of its includes.
224    fn get_diags(&self,doc: &super::Document) -> Vec<lsp::Diagnostic>;
225    fn get_folds(&self,doc: &super::Document) -> Vec<lsp::FoldingRange>;
226    fn err_warn_info_counts(&self) -> [usize;3];
227    fn eprint_lines_sexpr(&self,doc: &str);
228    /// If console start interactive entry, otherwise empty input pipe into string.
229    fn read_stdin(&self) -> String;
230}
231
232pub struct SemanticTokensBuilder {
233    last_pos: lsp::Position,
234    tok_map: HashMap<String,u32>,
235    tokens: Vec<lsp::SemanticToken>,
236    hex_re: regex::Regex
237}
238
239impl SemanticTokensBuilder {
240    pub fn new() -> Self {
241        let mut tok_map = HashMap::new();
242        let types = Self::get_token_types();
243        for i in 0..types.len() {
244            tok_map.insert(types[i].clone(),i as u32);
245        }
246        Self {
247            last_pos: lsp::Position::new(0,0),
248            tok_map,
249            tokens: Vec::new(),
250            hex_re: regex::Regex::new(r"\\x[0-9a-fA-F][0-9a-fA-F]").expect("bad regex")
251        }
252    }
253    pub fn get_token_types() ->Vec<String> {
254        TOKEN_TYPES.iter().map(|x| x.to_string()).collect()
255    }
256    pub fn reset(&mut self) {
257        self.tokens = Vec::new();
258        self.last_pos = lsp::Position::new(0,0);
259    }
260    pub fn clone_result(&self) -> Result<lsp::SemanticTokens,DYNERR> {
261        Ok(lsp::SemanticTokens {
262			result_id: None,
263			data: self.tokens.clone()
264		})
265    }
266    pub fn process_escapes(&mut self,curs: &tree_sitter::TreeCursor,line: &str,rng: lsp::Range,typ: &str) {
267        let pos0 = rng.start.character;
268        let mut pos = rng.start.character;
269        let txt = super::node_text(&curs.node(), line);
270        let re_clone = self.hex_re.clone();
271        for mtch in re_clone.find_iter(&txt) {
272            let esc_start =  pos0 + mtch.start() as u32;
273            let esc_end = pos0 + mtch.end() as u32;
274            let outer = lsp::Range::new(
275                lsp::Position::new(rng.start.line,pos),
276                lsp::Position::new(rng.start.line, esc_start)
277            );
278            self.push(outer,typ);
279            let emb = lsp::Range::new(
280                lsp::Position::new(rng.start.line, esc_start),
281                lsp::Position::new(rng.start.line, esc_end)
282            );
283            self.push(emb, "regexp");
284            pos = esc_end;
285        }
286        let outer = lsp::Range::new(
287            lsp::Position::new(rng.start.line, pos),
288            rng.end
289        );
290        self.push(outer, typ);
291    }
292    pub fn push(&mut self,rng: lsp::Range, typ: &str) {
293        if let Some(code) = self.tok_map.get(typ) {
294            if rng.start.line >= self.last_pos.line {
295                if rng.start.line == self.last_pos.line && rng.start.character < self.last_pos.character {
296                    return;
297                }
298                self.tokens.push(lsp::SemanticToken {
299                    delta_line: rng.start.line - self.last_pos.line,
300                    delta_start: match rng.start.line == self.last_pos.line {
301                        true => rng.start.character - self.last_pos.character,
302                        false => rng.start.character
303                    },
304                    length: rng.end.character - rng.start.character,
305                    token_type: *code,
306                    token_modifiers_bitset: 0
307                });
308                self.last_pos.line = rng.start.line;
309                self.last_pos.character = rng.start.character;
310            }
311        }
312    }
313}
314
315pub fn send_edit_req(connection: &lsp_server::Connection, doc: &lsp::TextDocumentItem, edits: Vec<lsp::TextEdit>) -> Result<(),String> {
316    let mut edit_list = Vec::new();
317    edit_list.push(lsp::TextDocumentEdit {
318        text_document: lsp::OptionalVersionedTextDocumentIdentifier::new(doc.uri.clone(), doc.version),
319        edits: edits.iter().map(|x| lsp::OneOf::Left(x.clone())).collect()
320    });
321    let ws_edit = lsp::WorkspaceEdit {
322        changes: None,
323        document_changes: Some(lsp::DocumentChanges::Edits(edit_list)),
324        change_annotations: None
325    };
326    // send the edit request
327    if let Ok(params) = serde_json::to_value(lsp::ApplyWorkspaceEditParams {label: None,edit: ws_edit}) {
328        let req = lsp_server::Request {
329            id: lsp_server::RequestId::from("renumber".to_string()),
330            method: lsp::request::ApplyWorkspaceEdit::METHOD.to_string(),
331            params
332        };
333        match connection.sender.send(lsp_server::Message::Request(req)) {
334            Ok(()) => Ok(()),
335            Err(_) => Err("could not send".to_string())
336        }
337    } else {
338        Err("could not parse".to_string())
339    }
340}
341
342pub fn basic_diag(range: lsp::Range,mess: &str,severity: lsp::DiagnosticSeverity) -> lsp::Diagnostic {
343    lsp::Diagnostic {
344        range,
345        severity: Some(severity),
346        code: None,
347        code_description: None,
348        source: None,
349        message: mess.to_string(),
350        related_information: None,
351        tags: None,
352        data: None
353    }
354}
355
356/// Get a path relative to the workspace path for display purposes.
357/// Only checks the first workspace folder.
358/// If there is any failure we keep the whole URI string.
359pub fn path_in_workspace(full: &lsp::Uri, ws_folder: &Vec<lsp::Uri>) -> String {
360    if ws_folder.len() == 0 {
361        return full.to_string();
362    }
363    let full_path = match super::pathbuf_from_uri(full) {
364        Ok(ans) => ans,
365        Err(_) => return full.to_string()
366    };
367    let ws_path = match super::pathbuf_from_uri(&ws_folder[0]) {
368        Ok(ans) => ans,
369        Err(_) => return full.to_string()
370    };
371    let e_full_canon = full_path.canonicalize();
372    let e_ws_canon = ws_path.canonicalize();
373    match (e_full_canon,e_ws_canon) {
374        (Ok(full_canon),Ok(ws_canon)) => {
375            let mut full_iter = full_canon.iter();
376            let mut ws_iter = ws_canon.iter();
377            while let Some(ws_node) = ws_iter.next() {
378                if let Some(node) = full_iter.next() {
379                    if node != ws_node {
380                        return full.to_string();
381                    }
382                } else {
383                    return full.to_string();
384                }
385            }
386            let mut ans = String::new();
387            while let Some(node) = full_iter.next() {
388                ans += &node.to_string_lossy();
389                ans += "/";
390            }
391            if ans.len() < 2 {
392                return full.to_string();
393            }
394            ans.pop();
395            ans
396        },
397        _ => full.to_string()
398    }
399}
400
401fn setup_env_logger(filt: log::LevelFilter, path: &str) {
402    if filt==log::LevelFilter::Off {
403        return;
404    }
405    let a2kit_logging_file = Box::new(std::fs::File::create(path).expect("failed to create log file"));
406    env_logger::Builder::new().format(|buf,record| {
407        writeln!(buf,"{}:{} [{}] - {}",record.file().unwrap_or("unknown"),
408            record.line().unwrap_or(0),
409            record.level(),
410            record.args()
411        )
412    })
413    .filter(Some("a2kit::lang"),filt)
414    .target(env_logger::Target::Pipe(a2kit_logging_file))
415    .init();
416}
417
418/// Parse the language server's command line arguments.
419/// Sets up logging based on the arguments, panics if log level or log file are invalid.
420/// As of this writing it returns only the `--suppress-tokens` status in `parse_args().0[0]`.
421pub fn parse_args() -> (Vec<bool>,Vec<String>) {
422    let mut log_level = log::LevelFilter::Off;
423    let mut log_file = "a2kit_log.txt".to_string();
424    let mut suppress_tokens = false;
425    
426    // process arguments
427    let mut args = std::env::args().into_iter();
428    args.next();
429    while let Some(val) = args.next() {
430        if &val == "--log-level" {
431            if let Some(val) = args.next() {
432                log_level = log::LevelFilter::from_str(&val).expect("invalid logging filter");
433            }
434        } else if &val == "--log-file" {
435            if let Some(val) = args.next() {
436                log_file = val;
437            }
438        } else if &val == "--suppress-tokens" {
439            // tokens will only be sent to client upon request
440            suppress_tokens = true;
441        }
442    }
443    setup_env_logger(log_level, &log_file);
444    (vec![suppress_tokens],vec![])
445}