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;
#[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;
}
}
struct InputReader<R> {
reader: R,
buffer: Vec<u8>,
}
impl<R> InputReader<R>
where
R: BufRead,
{
pub fn new(reader: R) -> Self {
Self {
reader,
buffer: Vec::new(),
}
}
fn next_line<'a>(&'a mut self) -> Result<Option<&'a [u8]>> {
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<R> Read for InputReader<R>
where
R: BufRead,
{
fn read(&mut self, buf: &mut [u8]) -> result::Result<usize, io::Error> {
self.reader.read(buf)
}
}
struct Channel<W> {
next_id: Arc<Mutex<u64>>,
output: Arc<Mutex<(Vec<u8>, W)>>,
}
impl<W> ::std::clone::Clone for Channel<W> {
fn clone(&self) -> Self {
Channel {
next_id: Arc::clone(&self.next_id),
output: Arc::clone(&self.output),
}
}
}
impl<W> Channel<W> {
pub fn new(writer: W) -> Self {
Self {
next_id: Arc::new(Mutex::new(0u64)),
output: Arc::new(Mutex::new((Vec::new(), writer))),
}
}
}
impl<W> Channel<W>
where
W: Write,
{
fn send_frame<T>(&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(())
}
fn send_notification<S: AsRef<str>, T>(&self, method: S, params: T) -> Result<()>
where
T: fmt::Debug + serde::Serialize,
{
let envelope = envelope::NotificationMessage::<T> {
jsonrpc: envelope::V2,
method: method.as_ref().to_string(),
params: Some(params),
};
self.send_frame(envelope)
}
fn send_request<S: AsRef<str>, T>(&self, method: S, params: T) -> Result<envelope::RequestId>
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::<T> {
jsonrpc: envelope::V2,
id: Some(id.clone()),
method: method.as_ref().to_string(),
params: params,
};
self.send_frame(envelope)?;
Ok(id)
}
fn send<T>(&self, request_id: Option<envelope::RequestId>, message: T) -> Result<()>
where
T: fmt::Debug + serde::Serialize,
{
let envelope = envelope::ResponseMessage::<T, ()> {
jsonrpc: envelope::V2,
id: request_id,
result: Some(message),
error: None,
};
self.send_frame(envelope)
}
fn send_error<D>(
&self,
request_id: Option<envelope::RequestId>,
error: envelope::ResponseError<D>,
) -> 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<L>
where
L: Send,
{
log: Mutex<L>,
}
impl<L> Logger<L>
where
L: Send,
{
pub fn new(log: L) -> Self {
Self {
log: Mutex::new(log),
}
}
}
impl<L> log::Log for Logger<L>
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();
}
}
}
struct NotificationLogger<W>
where
W: Send,
{
channel: Channel<W>,
}
impl<W> NotificationLogger<W>
where
W: Send,
{
pub fn new(channel: Channel<W>) -> Self {
Self { channel }
}
}
impl<W> log::Log for NotificationLogger<W>
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<L: 'static, R, W: 'static>(
log: Option<L>,
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<R, W: 'static>(reader: R, channel: Channel<W>) -> 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(())
}
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]
}
struct Server<R, W> {
workspace: Option<RefCell<Workspace>>,
headers: Headers,
reader: InputReader<BufReader<R>>,
channel: Channel<W>,
ctx: Rc<Context>,
expected: HashMap<envelope::RequestId, &'static str>,
}
impl<R, W> Server<R, W>
where
R: Read,
W: Write,
{
pub fn new(reader: R, channel: Channel<W>, ctx: Rc<Context>) -> Self {
Self {
workspace: None,
headers: Headers::new(),
reader: InputReader::new(BufReader::new(reader)),
channel,
ctx,
expected: HashMap::new(),
}
}
fn read_headers(&mut self) -> Result<bool> {
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::<u32>()
.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))?
};
if message.get("method").is_some() {
self.handle_request(message)?;
} else {
self.handle_response(message)?;
}
}
}
}
Ok(())
}
fn handle_request(&mut self, message: json::Value) -> Result<()> {
let request = envelope::RequestMessage::<json::Value>::deserialize(message)
.map_err(|e| format!("failed to deserialize request: {}", e))?;
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(())
}
fn try_handle_request(&mut self, request: envelope::RequestMessage<json::Value>) -> 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" => {
}
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(())
}
fn handle_response(&mut self, message: json::Value) -> Result<()> {
let response = envelope::ResponseMessage::<json::Value, json::Value>::deserialize(message)
.map_err(|e| format!("failed to deserialize request: {}", e))?;
if let Some(_) = response.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::<ty::MessageActionItem>::deserialize(result)?;
self.handle_project_init(response)?;
}
"reproto/projectAddMissing" => {
let result = match response.result {
Some(result) => result,
None => return Ok(()),
};
let response = Option::<ty::MessageActionItem>::deserialize(result)?;
self.handle_project_add_missing(response)?;
}
_ => {}
}
Ok(())
}
fn handle_project_init(&mut self, response: Option<ty::MessageActionItem>) -> 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(())
}
fn handle_project_add_missing(
&mut self,
response: Option<ty::MessageActionItem>,
) -> 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(())
}
fn initialize(
&mut self,
request_id: Option<envelope::RequestId>,
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(())
}
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(())
}
fn workspace_did_change_configuration(
&mut self,
_: Option<envelope::RequestId>,
_: ty::DidChangeConfigurationParams,
) -> Result<()> {
Ok(())
}
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(())
}
fn send_diagnostics(
&self,
url: &Url,
file: &LoadedFile,
diagnostics_by_url: &HashMap<Url, Diagnostics>,
) -> 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(())
}
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(())
}
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 {
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
}
None => {
rope.remove(..);
0
}
};
if content_change.text != "" {
rope.insert(start, &content_change.text);
}
}
}
None => return Ok(()),
}
workspace.reload()?;
}
self.send_workspace_diagnostics()?;
Ok(())
}
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()?;
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(())
}
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(())
}
fn text_document_completion(
&self,
request_id: Option<envelope::RequestId>,
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(())
}
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(())
}
fn text_document_definition(
&self,
request_id: Option<envelope::RequestId>,
params: ty::TextDocumentPositionParams,
) -> Result<()> {
let mut response: Option<ty::request::GotoDefinitionResponse> = None;
self.definition(params, &mut response)?;
self.channel.send(request_id, response)?;
Ok(())
}
fn text_document_rename(
&self,
request_id: Option<envelope::RequestId>,
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<ty::WorkspaceEdit> = None;
if let Some(rename) = workspace.find_rename(&url, params.position) {
let edits = match rename {
RenameResult::Local { ranges } => setup_edits(ranges, new_name.as_str()),
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<Range>, new_text: &str) -> Vec<ty::TextEdit> {
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
}
}
fn definition(
&self,
params: ty::TextDocumentPositionParams,
response: &mut Option<ty::request::GotoDefinitionResponse>,
) -> 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(())
}
}
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 }
}