extern crate languageserver_types as ty; extern crate linked_hash_map; #[macro_use] extern crate log; extern crate reproto_ast as ast; extern crate reproto_core as core; extern crate reproto_env as env; extern crate reproto_lexer as lexer; extern crate reproto_manifest as manifest; extern crate reproto_parser as parser; extern crate serde; extern crate serde_json as json; #[macro_use] extern crate serde_derive; extern crate ropey; extern crate url; extern crate url_serde; mod envelope; mod workspace; use self::ContentType::*; use self::workspace::{Completion, Jump, LoadedFile, Range, RenameResult, Workspace}; use core::errors::Result; use core::{Context, ContextItem, Diagnostics, Encoding, Loc, RealFilesystem, Source}; use ropey::Rope; use serde::Deserialize; use std::cell::RefCell; use std::collections::HashMap; use std::fmt; use std::io::{self, BufRead, BufReader, Read, Write}; use std::ops::DerefMut; use std::path::Path; use std::rc::Rc; use std::result; use std::sync::{Arc, Mutex}; use url::Url; /// newtype to serialize URLs #[derive(Debug, Serialize)] struct SerdeUrl(#[serde(with = "url_serde")] Url); #[derive(Debug)] enum ContentType { JsonRPC, } #[derive(Debug)] struct Headers { content_type: ContentType, content_length: u32, } impl Headers { pub fn new() -> Self { Self { content_type: JsonRPC, content_length: 0u32, } } fn clear(&mut self) { self.content_type = JsonRPC; self.content_length = 0; } } /// Reads input stream for server. struct InputReader { reader: R, buffer: Vec, } impl InputReader where R: BufRead, { pub fn new(reader: R) -> Self { Self { reader, buffer: Vec::new(), } } fn next_line<'a>(&'a mut self) -> Result> { self.buffer.clear(); self.reader.read_until('\n' as u8, &mut self.buffer)?; if self.buffer.is_empty() { return Ok(None); } Ok(Some(trim(&self.buffer))) } } impl Read for InputReader where R: BufRead, { fn read(&mut self, buf: &mut [u8]) -> result::Result { self.reader.read(buf) } } /// A language server channel, taking care of locking and sending notifications. struct Channel { /// Request id allocation. next_id: Arc>, /// A writer and buffer pair. output: Arc, W)>>, } impl ::std::clone::Clone for Channel { fn clone(&self) -> Self { Channel { next_id: Arc::clone(&self.next_id), output: Arc::clone(&self.output), } } } impl Channel { pub fn new(writer: W) -> Self { Self { next_id: Arc::new(Mutex::new(0u64)), output: Arc::new(Mutex::new((Vec::new(), writer))), } } } impl Channel where W: Write, { /// Send a complete frame. fn send_frame(&self, frame: T) -> Result<()> where T: fmt::Debug + serde::Serialize, { debug!("send frame: {:#?}", frame); let mut guard = self.output.lock().map_err(|_| "lock poisoned")?; let &mut (ref mut buffer, ref mut writer) = guard.deref_mut(); buffer.clear(); json::to_writer(&mut *buffer, &frame)?; write!(writer, "Content-Length: {}\r\n\r\n", buffer.len())?; writer.write_all(buffer)?; writer.flush()?; Ok(()) } /// Send a notification. fn send_notification, T>(&self, method: S, params: T) -> Result<()> where T: fmt::Debug + serde::Serialize, { let envelope = envelope::NotificationMessage:: { jsonrpc: envelope::V2, method: method.as_ref().to_string(), params: Some(params), }; self.send_frame(envelope) } /// Send a request. fn send_request, T>(&self, method: S, params: T) -> Result where T: fmt::Debug + serde::Serialize, { let id = { let mut next_id = self.next_id.lock().map_err(|_| "id allocation poisoned")?; let id = *next_id; *next_id = id + 1; envelope::RequestId::Number(id) }; let envelope = envelope::RequestMessage:: { jsonrpc: envelope::V2, id: Some(id.clone()), method: method.as_ref().to_string(), params: params, }; self.send_frame(envelope)?; Ok(id) } /// Send a response message. fn send(&self, request_id: Option, message: T) -> Result<()> where T: fmt::Debug + serde::Serialize, { let envelope = envelope::ResponseMessage:: { jsonrpc: envelope::V2, id: request_id, result: Some(message), error: None, }; self.send_frame(envelope) } /// Send an error. fn send_error( &self, request_id: Option, error: envelope::ResponseError, ) -> Result<()> where D: fmt::Debug + serde::Serialize, { let envelope = envelope::ResponseMessage::<(), D> { jsonrpc: envelope::V2, id: request_id, result: None, error: Some(error), }; self.send_frame(envelope) } } pub struct Logger where L: Send, { log: Mutex, } impl Logger where L: Send, { pub fn new(log: L) -> Self { Self { log: Mutex::new(log), } } } impl log::Log for Logger where L: Send + Write, { fn enabled(&self, metadata: &log::LogMetadata) -> bool { metadata.level() <= log::LogLevel::Debug } fn log(&self, record: &log::LogRecord) { if self.enabled(record.metadata()) { let mut lock = self.log.lock().expect("poisoned lock"); writeln!(lock, "{}: {}", record.level(), record.args()).unwrap(); } } } /// Logger that sends logs as notifications to client. struct NotificationLogger where W: Send, { channel: Channel, } impl NotificationLogger where W: Send, { pub fn new(channel: Channel) -> Self { Self { channel } } } impl log::Log for NotificationLogger where W: Send + Write, { fn enabled(&self, metadata: &log::LogMetadata) -> bool { metadata.level() <= log::LogLevel::Debug } fn log(&self, record: &log::LogRecord) { use log::LogLevel::*; if !self.enabled(record.metadata()) { return; } let typ = match record.level() { Error => ty::MessageType::Error, Warn => ty::MessageType::Warning, Info => ty::MessageType::Info, _ => ty::MessageType::Log, }; let notification = ty::LogMessageParams { typ, message: record.args().to_string(), }; self.channel .send_notification("window/logMessage", notification) .expect("failed to send notification"); } } pub fn server( log: Option, reader: R, writer: W, level: log::LogLevelFilter, ) -> Result<()> where L: Send + Write, R: Read, W: Send + Write, { let channel = Channel::new(writer); if let Some(log) = log { let logger = Logger::new(log); log::set_logger(|max_level| { max_level.set(level); Box::new(logger) })?; } else { log::set_logger(|max_level| { max_level.set(level); Box::new(NotificationLogger::new(channel.clone())) })?; } match try_server(reader, channel) { Err(e) => { error!("error: {}", e.display()); for cause in e.causes().skip(1) { error!("caused by: {}", cause.display()); } return Err(e); } Ok(()) => { return Ok(()); } } } fn try_server(reader: R, channel: Channel) -> Result<()> where R: Read, W: Send + Write, { let ctx = Context::new(Box::new(RealFilesystem::new())); let mut server = Server::new(reader, channel, Rc::new(ctx)); server.run()?; Ok(()) } /// Trim the string from whitespace. fn trim(data: &[u8]) -> &[u8] { let s = data.iter() .position(|b| *b != b'\n' && *b != b'\r' && *b != b' ') .unwrap_or(data.len()); let data = &data[s..]; let e = data.iter() .rev() .position(|b| *b != b'\n' && *b != b'\r' && *b != b' ') .map(|p| data.len() - p) .unwrap_or(0usize); &data[..e] } /// Server abstraction struct Server { workspace: Option>, headers: Headers, reader: InputReader>, channel: Channel, ctx: Rc, /// Expected responses. expected: HashMap, } impl Server where R: Read, W: Write, { pub fn new(reader: R, channel: Channel, ctx: Rc) -> Self { Self { workspace: None, headers: Headers::new(), reader: InputReader::new(BufReader::new(reader)), channel, ctx, expected: HashMap::new(), } } /// Read headers. fn read_headers(&mut self) -> Result { self.headers.clear(); loop { let line = self.reader.next_line()?; let line = match line { Some(line) => line, None => return Ok(false), }; if line == b"" { break; } let mut parts = line.splitn(2, |b| *b == b':'); let (key, value) = match (parts.next(), parts.next()) { (Some(key), Some(value)) => (trim(key), trim(value)), out => { return Err(format!("bad header: {:?}", out).into()); } }; match key { b"Content-Type" => match value { b"application/vscode-jsonrpc; charset=utf-8" => { self.headers.content_type = JsonRPC; } value => { return Err(format!("bad value: {:?}", value).into()); } }, b"Content-Length" => { let value = ::std::str::from_utf8(value) .map_err(|e| format!("bad content-length: {:?}: {}", value, e))?; let value = value .parse::() .map_err(|e| format!("bad content-length: {}: {}", value, e))?; self.headers.content_length = value; } key => { return Err(format!("bad header: {:?}", key).into()); } } } Ok(true) } pub fn run(&mut self) -> Result<()> { loop { if !self.read_headers()? { break; } if self.headers.content_length == 0 { continue; } match self.headers.content_type { JsonRPC => { let message: json::Value = { let reader = (&mut self.reader).take(self.headers.content_length as u64); json::from_reader(reader) .map_err(|e| format!("failed to deserialize message: {}", e))? }; // requests if message.get("method").is_some() { self.handle_request(message)?; } else { self.handle_response(message)?; } } } } Ok(()) } /// Handle a request. fn handle_request(&mut self, message: json::Value) -> Result<()> { let request = envelope::RequestMessage::::deserialize(message) .map_err(|e| format!("failed to deserialize request: {}", e))?; // in case we need it to report errors. let id = request.id.clone(); debug!("received: {:#?}", request); if let Err(e) = self.try_handle_request(request) { self.channel.send_error( id, envelope::ResponseError { code: envelope::Code::InternalError, message: e.display().to_string(), data: Some(()), }, )?; } Ok(()) } /// Inner handler which is guarded against errors. /// /// Any error returned here will result in an error being sent _back_ to the language client as /// a ResponseError. fn try_handle_request(&mut self, request: envelope::RequestMessage) -> Result<()> { match request.method.as_str() { "initialize" => { let params = ty::InitializeParams::deserialize(request.params)?; self.initialize(request.id, params)?; } "initialized" => { let params = ty::InitializedParams::deserialize(request.params)?; self.initialized(params)?; } "shutdown" => { self.shutdown()?; } "textDocument/didChange" => { let params = ty::DidChangeTextDocumentParams::deserialize(request.params)?; self.text_document_did_change(params)?; } "textDocument/didOpen" => { let params = ty::DidOpenTextDocumentParams::deserialize(request.params)?; self.text_document_did_open(params)?; } "textDocument/didClose" => { let params = ty::DidCloseTextDocumentParams::deserialize(request.params)?; self.text_document_did_close(params)?; } "textDocument/didSave" => { let params = ty::DidSaveTextDocumentParams::deserialize(request.params)?; self.text_document_did_save(params)?; } "textDocument/completion" => { let params = ty::CompletionParams::deserialize(request.params)?; self.text_document_completion(request.id, params)?; } "textDocument/definition" => { let params = ty::TextDocumentPositionParams::deserialize(request.params)?; self.text_document_definition(request.id, params)?; } "textDocument/rename" => { let params = ty::RenameParams::deserialize(request.params)?; self.text_document_rename(request.id, params)?; } "workspace/didChangeConfiguration" => { let params = ty::DidChangeConfigurationParams::deserialize(request.params)?; self.workspace_did_change_configuration(request.id, params)?; } "completionItem/resolve" => { let params = ty::CompletionItem::deserialize(request.params)?; self.completion_item_resolve(params)?; } "$/cancelRequest" => { // ignore } method => { error!("unsupported method: {}", method); self.channel.send_error( request.id, envelope::ResponseError { code: envelope::Code::MethodNotFound, message: "No such method".to_string(), data: Some(()), }, )?; } } Ok(()) } /// Handle a response. fn handle_response(&mut self, message: json::Value) -> Result<()> { // responses let response = envelope::ResponseMessage::::deserialize(message) .map_err(|e| format!("failed to deserialize request: {}", e))?; if let Some(_) = response.error { // TODO: handle error return Ok(()); } let id = match response.id { Some(ref id) => id, None => return Ok(()), }; let method = match self.expected.remove(&id) { Some(method) => method, None => { debug!("no handle for id: {:?}", id); return Ok(()); } }; debug!("response: {:?} {:#?}", method, response); match method { "reproto/projectInit" => { let result = match response.result { Some(result) => result, None => return Ok(()), }; let response = Option::::deserialize(result)?; self.handle_project_init(response)?; } "reproto/projectAddMissing" => { let result = match response.result { Some(result) => result, None => return Ok(()), }; let response = Option::::deserialize(result)?; self.handle_project_add_missing(response)?; } _ => {} } Ok(()) } /// Handle the response of `reproto/projectInit`. fn handle_project_init(&mut self, response: Option) -> Result<()> { let response = match response { Some(response) => response, None => return Ok(()), }; if let Some(workspace) = self.workspace.as_ref() { let mut workspace = workspace .try_borrow_mut() .map_err(|_| "failed to access mutable workspace")?; let handle = self.ctx.filesystem(Some(&workspace.root_path))?; if response.title == "Initialize project" { info!("Initializing Project!"); workspace.initialize(handle.as_ref())?; let manifest_url = workspace.manifest_url()?; self.channel .send_notification("$/openUrl", SerdeUrl(manifest_url))?; } } Ok(()) } /// Handle the response of `reproto/projectAddMissing`. fn handle_project_add_missing( &mut self, response: Option, ) -> Result<()> { let response = match response { Some(response) => response, None => return Ok(()), }; if let Some(workspace) = self.workspace.as_ref() { let mut workspace = workspace .try_borrow() .map_err(|_| "failed to access mutable workspace")?; if response.title == "Open project manifest" { let manifest_url = workspace.manifest_url()?; self.channel .send_notification("$/openUrl", SerdeUrl(manifest_url))?; } } Ok(()) } fn shutdown(&mut self) -> Result<()> { Ok(()) } /// Handler for `initialize`. fn initialize( &mut self, request_id: Option, params: ty::InitializeParams, ) -> Result<()> { if let Some(path) = params.root_path.as_ref() { let path = Path::new(path.as_str()); let path = path.canonicalize() .map_err(|_| format!("could not canonicalize root path: {}", path.display()))?; let mut workspace = Workspace::new(path, self.ctx.clone()); self.workspace = Some(RefCell::new(workspace)); } let result = ty::InitializeResult { capabilities: ty::ServerCapabilities { text_document_sync: Some(ty::TextDocumentSyncCapability::Kind( ty::TextDocumentSyncKind::Incremental, )), completion_provider: Some(ty::CompletionOptions { trigger_characters: Some(vec![":".into(), ".".into()]), ..ty::CompletionOptions::default() }), definition_provider: Some(true), rename_provider: Some(true), ..ty::ServerCapabilities::default() }, }; self.channel.send(request_id, result)?; Ok(()) } /// Handler for `initialized`. fn initialized(&mut self, _params: ty::InitializedParams) -> Result<()> { if let Some(workspace) = self.workspace.as_ref() { let mut workspace = workspace .try_borrow_mut() .map_err(|_| "failed to access mutable workspace")?; debug!("loading project: {}", workspace.root_path.display()); workspace.reload()?; } self.send_workspace_diagnostics()?; Ok(()) } /// Handler for `workspace/didChangeConfiguration`. fn workspace_did_change_configuration( &mut self, _: Option, _: ty::DidChangeConfigurationParams, ) -> Result<()> { Ok(()) } /// Send all diagnostics for a workspace. fn send_workspace_diagnostics(&self) -> Result<()> { let mut diagnostics_by_url = HashMap::new(); for item in self.ctx.items()?.iter() { match *item { ContextItem::Diagnostics { ref diagnostics } => { if let Some(url) = diagnostics.source.url() { diagnostics_by_url.insert(url, diagnostics.clone()); } } } } if let Some(workspace) = self.workspace.as_ref() { let workspace = workspace .try_borrow() .map_err(|_| "failed to access workspace immutably")?; for (url, file) in workspace.files() { self.send_diagnostics(url, &file, &diagnostics_by_url)?; } } Ok(()) } /// Send diagnostics for a single file. fn send_diagnostics( &self, url: &Url, file: &LoadedFile, diagnostics_by_url: &HashMap, ) -> Result<()> { let mut diagnostics = Vec::new(); let by_url = diagnostics_by_url.get(url); let by_url_chain = by_url.iter().flat_map(|d| d.items()); for d in file.diag.items().chain(by_url_chain) { match *d { core::Diagnostic::Error(ref span, ref m) => { let (start, end) = file.diag.source.span_to_range(*span, Encoding::Utf16)?; let range = convert_range(start, end); let d = ty::Diagnostic { range: range, message: m.to_string(), severity: Some(ty::DiagnosticSeverity::Error), ..ty::Diagnostic::default() }; diagnostics.push(d); } core::Diagnostic::Info(ref span, ref m) => { let (start, end) = file.diag.source.span_to_range(*span, Encoding::Utf16)?; let range = convert_range(start, end); let d = ty::Diagnostic { range: range, message: m.to_string(), severity: Some(ty::DiagnosticSeverity::Information), ..ty::Diagnostic::default() }; diagnostics.push(d); } _ => {} } } self.channel.send_notification( "textDocument/publishDiagnostics", ty::PublishDiagnosticsParams { uri: url.clone(), diagnostics: diagnostics, }, )?; Ok(()) } /// Handler for `textDocument/didSave`. fn text_document_did_save(&self, _: ty::DidSaveTextDocumentParams) -> Result<()> { if let Some(workspace) = self.workspace.as_ref() { let mut workspace = workspace .try_borrow_mut() .map_err(|_| "failed to access mutable workspace")?; workspace.reload()?; } self.send_workspace_diagnostics()?; Ok(()) } /// Handler for `textDocument/didChange`. fn text_document_did_change(&self, params: ty::DidChangeTextDocumentParams) -> Result<()> { let text_document = params.text_document; let url = text_document.uri; { let workspace = match self.workspace.as_ref() { Some(workspace) => workspace, None => return Ok(()), }; let mut workspace = workspace .try_borrow_mut() .map_err(|_| "failed to access mutable workspace")?; if params.content_changes.is_empty() { return Ok(()); } match workspace.edited_files.get_mut(&url) { Some(file) => { let rope = match file.diag.source.as_mut_rope() { Some(rope) => rope, None => return Ok(()), }; for content_change in ¶ms.content_changes { let start = match content_change.range { // replace range Some(ref range) => { let start = &range.start; let end = &range.end; let start = rope.line_to_char(start.line as usize) + start.character as usize; let end = rope.line_to_char(end.line as usize) + end.character as usize; rope.remove(start..end); start } // replace all None => { rope.remove(..); 0 } }; if content_change.text != "" { rope.insert(start, &content_change.text); } } } None => return Ok(()), } workspace.reload()?; } self.send_workspace_diagnostics()?; Ok(()) } /// Handler for `textDocument/didOpen`. fn text_document_did_open(&mut self, params: ty::DidOpenTextDocumentParams) -> Result<()> { let text_document = params.text_document; let url = text_document.uri; let text = text_document.text; if let Some(workspace) = self.workspace.as_ref() { let rope = Rope::from_str(&text); let source = Source::rope(url.clone(), rope); let mut loaded = LoadedFile::new(url.clone(), source.clone()); let mut workspace = workspace .try_borrow_mut() .map_err(|_| "failed to access mutable workspace")?; workspace.edited_files.insert(url.clone(), loaded); workspace.reload()?; // warn if the currently opened file is not part of workspace. if !workspace.loaded_files.contains(&url) { let manifest_url = workspace.manifest_url()?; if workspace.manifest_path.is_file() { let mut actions = Vec::new(); actions.push(ty::MessageActionItem { title: "Ignore".to_string(), }); actions.push(ty::MessageActionItem { title: "Open project manifest".to_string(), }); let message = format!( "This file is not part of project, consider updating the [packages] \ section in reproto.toml" ); let id = self.channel.send_request( "window/showMessageRequest", ty::ShowMessageRequestParams { typ: ty::MessageType::Warning, message: message, actions: Some(actions), }, )?; self.expected.insert(id, "reproto/projectAddMissing"); } else { let mut actions = Vec::new(); warn!("missing reproto manifest: {}", manifest_url); actions.push(ty::MessageActionItem { title: "Ignore".to_string(), }); actions.push(ty::MessageActionItem { title: "Initialize project".to_string(), }); let message = format!("Workspace does not have a reproto manifest!"); let id = self.channel.send_request( "window/showMessageRequest", ty::ShowMessageRequestParams { typ: ty::MessageType::Warning, message: message, actions: Some(actions), }, )?; self.expected.insert(id, "reproto/projectInit"); } } } self.send_workspace_diagnostics()?; Ok(()) } /// Handler for `textDocument/didClose`. fn text_document_did_close(&self, params: ty::DidCloseTextDocumentParams) -> Result<()> { let text_document = params.text_document; if let Some(workspace) = self.workspace.as_ref() { let url = text_document.uri; let mut workspace = workspace .try_borrow_mut() .map_err(|_| "failed to access mutable workspace")?; workspace.edited_files.remove(&url); workspace.reload()?; } self.send_workspace_diagnostics()?; Ok(()) } /// Handler for `textDocument/completion`. fn text_document_completion( &self, request_id: Option, params: ty::CompletionParams, ) -> Result<()> { let mut response = ty::CompletionList { ..ty::CompletionList::default() }; self.completion_items(params, &mut response)?; self.channel.send(request_id, response)?; Ok(()) } /// Populate completion items for the given request. fn completion_items( &self, params: ty::CompletionParams, list: &mut ty::CompletionList, ) -> Result<()> { let url = params.text_document.uri; let workspace = match self.workspace.as_ref() { Some(workspace) => workspace, None => return Ok(()), }; let workspace = workspace .try_borrow() .map_err(|_| "failed to access immutable workspace")?; let (file, value) = match workspace.find_completion(&url, params.position) { Some(v) => v, None => return Ok(()), }; debug!("type completion: {:?}", value); match *value { Completion::Package { ref results, .. } => { for r in results { list.items.push(ty::CompletionItem { label: r.to_string(), kind: Some(ty::CompletionItemKind::Module), ..ty::CompletionItem::default() }); } } Completion::Any => { let candidates = vec![ "string", "bytes", "u32", "u64", "i32", "i64", "float", "double", "datetime", "any", ]; for (prefix, value) in &file.prefixes { list.items.push(ty::CompletionItem { label: format!("{}::", prefix), kind: Some(ty::CompletionItemKind::Module), detail: Some(value.package.to_string()), ..ty::CompletionItem::default() }); } for c in candidates { list.items.push(ty::CompletionItem { label: c.to_string(), kind: Some(ty::CompletionItemKind::Keyword), ..ty::CompletionItem::default() }); } for symbol in file.symbols.keys() { if symbol.len() != 1 { continue; } let symbol = symbol.join("::"); list.items.push(ty::CompletionItem { label: symbol, kind: Some(ty::CompletionItemKind::Class), ..ty::CompletionItem::default() }); } } Completion::Absolute { ref prefix, ref path, ref suffix, } => { let file = if let Some(ref prefix) = *prefix { match file.prefixes .get(prefix) .and_then(|p| workspace.packages.get(&p.package)) .and_then(|url| workspace.file(url)) { Some(file) => file, None => return Ok(()), } } else { file }; if let Some(symbols) = file.symbols.get(path) { if let Some(ref suffix) = *suffix { for s in symbols.iter().filter(|s| { s.name.to_lowercase().starts_with(&suffix.to_lowercase()) && Loc::borrow(&s.name) != suffix }) { list.items.push(ty::CompletionItem { label: s.name.to_string(), kind: Some(ty::CompletionItemKind::Class), documentation: s.to_documentation(), ..ty::CompletionItem::default() }); } } else { for s in symbols { list.items.push(ty::CompletionItem { label: s.name.to_string(), kind: Some(ty::CompletionItemKind::Class), documentation: s.to_documentation(), ..ty::CompletionItem::default() }); } } }; } } Ok(()) } fn completion_item_resolve(&self, _: ty::CompletionItem) -> Result<()> { Ok(()) } /// Handler for `textDocument/definition`. fn text_document_definition( &self, request_id: Option, params: ty::TextDocumentPositionParams, ) -> Result<()> { let mut response: Option = None; self.definition(params, &mut response)?; self.channel.send(request_id, response)?; Ok(()) } /// Handler for renaming fn text_document_rename( &self, request_id: Option, params: ty::RenameParams, ) -> Result<()> { let workspace = match self.workspace.as_ref() { Some(workspace) => workspace, None => return Err("no workspace".into()), }; let workspace = workspace .try_borrow() .map_err(|_| "failed to access immutable workspace")?; let url = params.text_document.uri; let new_name = params.new_name; let mut edit: Option = None; if let Some(rename) = workspace.find_rename(&url, params.position) { let edits = match rename { // all edits in the same file as where the rename was requested. RenameResult::Local { ranges } => setup_edits(ranges, new_name.as_str()), // same as Local, but we also want to refactor the package position to include the // new alias. RenameResult::ImplicitPackage { ranges, position } => { let mut edits = setup_edits(ranges, new_name.as_str()); edits.push(ty::TextEdit { range: convert_range(position, position), new_text: format!(" as {}", new_name), }); edits } }; let changes = vec![ ty::TextDocumentEdit { text_document: ty::VersionedTextDocumentIdentifier { uri: url.clone(), version: None, }, edits: edits, }, ]; edit = Some(ty::WorkspaceEdit { document_changes: Some(changes), ..ty::WorkspaceEdit::default() }); } self.channel.send(request_id, edit)?; return Ok(()); fn setup_edits(ranges: &Vec, new_text: &str) -> Vec { let mut edits = Vec::new(); for range in ranges { edits.push(ty::TextEdit { range: convert_range(range.start, range.end), new_text: new_text.to_string(), }); } edits } } /// Populate the goto definition response. fn definition( &self, params: ty::TextDocumentPositionParams, response: &mut Option, ) -> Result<()> { let url = params.text_document.uri; let workspace = match self.workspace.as_ref() { Some(workspace) => workspace, None => return Ok(()), }; let workspace = workspace .try_borrow() .map_err(|_| "failed to access immutable workspace")?; let (file, value) = match workspace.find_jump(&url, params.position) { Some(v) => v, None => return Ok(()), }; debug!("definition: {}: {:?}", file.url, value); match *value { Jump::Absolute { ref prefix, ref path, } => { let (uri, file) = if let Some(ref prefix) = *prefix { let prefix = match file.prefixes.get(prefix) { Some(prefix) => prefix, None => return Ok(()), }; let url = match workspace.packages.get(&prefix.package) { Some(url) => url, None => return Ok(()), }; match workspace.file(url) { Some(file) => (url.clone(), file), None => return Ok(()), } } else { (url, file) }; let span = match file.symbol.get(path) { Some(span) => *span, None => return Ok(()), }; let (start, end) = file.diag.source.span_to_range(span, Encoding::Utf16)?; let range = convert_range(start, end); let location = ty::Location { uri, range }; *response = Some(ty::request::GotoDefinitionResponse::Scalar(location)); } Jump::Package { ref prefix } => { let prefix = match file.prefixes.get(prefix) { Some(prefix) => prefix, None => return Ok(()), }; let uri = match workspace.packages.get(&prefix.package) { Some(url) => url.clone(), None => return Ok(()), }; let range = ty::Range::default(); let location = ty::Location { uri, range }; *response = Some(ty::request::GotoDefinitionResponse::Scalar(location)); } Jump::Prefix { ref prefix } => { let prefix = match file.prefixes.get(prefix) { Some(prefix) => prefix, None => return Ok(()), }; let (start, end) = file.diag .source .span_to_range(prefix.span, Encoding::Utf16)?; let range = convert_range(start, end); let location = ty::Location { uri: url.clone(), range, }; *response = Some(ty::request::GotoDefinitionResponse::Scalar(location)); } } Ok(()) } } /// Convert an internal range into a language-server range. fn convert_range(start: core::Position, end: core::Position) -> ty::Range { let start = ty::Position { line: start.line as u64, character: start.col as u64, }; let end = ty::Position { line: end.line as u64, character: end.col as u64, }; ty::Range { start, end } }