From 9f6cf42c5fd0bd98dd3445239f2c6414e8fd9324 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 15 Aug 2018 17:24:20 +0300 Subject: [PATCH] Switch to file ids --- crates/libanalysis/src/lib.rs | 10 +-- crates/server/Cargo.toml | 1 + crates/server/src/conv.rs | 76 +++++++++++++++++---- crates/server/src/main.rs | 6 +- crates/server/src/main_loop/handlers.rs | 91 +++++++++++++++---------- crates/server/src/main_loop/mod.rs | 78 ++++++++++++--------- crates/server/src/path_map.rs | 47 +++++++++++++ crates/server/src/util.rs | 33 --------- 8 files changed, 215 insertions(+), 127 deletions(-) create mode 100644 crates/server/src/path_map.rs delete mode 100644 crates/server/src/util.rs diff --git a/crates/libanalysis/src/lib.rs b/crates/libanalysis/src/lib.rs index fc0b9ee9a85..7c52080cb8f 100644 --- a/crates/libanalysis/src/lib.rs +++ b/crates/libanalysis/src/lib.rs @@ -37,7 +37,6 @@ pub type Result = ::std::result::Result; const INDEXING_THRESHOLD: usize = 128; pub struct WorldState { - next_file_id: u32, data: Arc } @@ -47,12 +46,11 @@ pub struct World { } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct FileId(u32); +pub struct FileId(pub u32); impl WorldState { pub fn new() -> WorldState { WorldState { - next_file_id: 0, data: Arc::new(WorldData::default()) } } @@ -61,12 +59,6 @@ impl WorldState { World { data: self.data.clone() } } - pub fn new_file_id(&mut self) -> FileId { - let id = FileId(self.next_file_id); - self.next_file_id += 1; - id - } - pub fn change_file(&mut self, file_id: FileId, text: Option) { self.change_files(::std::iter::once((file_id, text))); } diff --git a/crates/server/Cargo.toml b/crates/server/Cargo.toml index 0ad193b8a18..058bf36d238 100644 --- a/crates/server/Cargo.toml +++ b/crates/server/Cargo.toml @@ -16,6 +16,7 @@ log = "0.4.3" url_serde = "0.2.0" languageserver-types = "0.49.0" walkdir = "2.2.0" +im = { version = "11.0.1", features = ["arc"] } text_unit = { version = "0.1.2", features = ["serde"] } libsyntax2 = { path = "../libsyntax2" } diff --git a/crates/server/src/conv.rs b/crates/server/src/conv.rs index 3ddce5fb3ec..bbe512ece62 100644 --- a/crates/server/src/conv.rs +++ b/crates/server/src/conv.rs @@ -1,10 +1,12 @@ -use std::path::Path; - -use languageserver_types::{Range, SymbolKind, Position, TextEdit, Location, Url}; +use languageserver_types::{ + Range, SymbolKind, Position, TextEdit, Location, Url, + TextDocumentIdentifier, VersionedTextDocumentIdentifier, TextDocumentItem, +}; use libeditor::{LineIndex, LineCol, Edit, AtomEdit}; use libsyntax2::{SyntaxKind, TextUnit, TextRange}; +use libanalysis::FileId; -use Result; +use {Result, PathMap}; pub trait Conv { type Output; @@ -115,20 +117,64 @@ impl ConvWith for AtomEdit { } } -impl<'a> TryConvWith for (&'a Path, TextRange) { - type Ctx = LineIndex; - type Output = Location; - - fn try_conv_with(self, line_index: &LineIndex) -> Result { - let loc = Location::new( - Url::from_file_path(self.0) - .map_err(|()| format_err!("can't convert path to url: {}", self.0.display()))?, - self.1.conv_with(line_index), - ); - Ok(loc) +impl<'a> TryConvWith for &'a Url { + type Ctx = PathMap; + type Output = FileId; + fn try_conv_with(self, path_map: &PathMap) -> Result { + let path = self.to_file_path() + .map_err(|()| format_err!("invalid uri: {}", self))?; + path_map.get_id(&path).ok_or_else(|| format_err!("unknown file: {}", path.display())) } } +impl TryConvWith for FileId { + type Ctx = PathMap; + type Output = Url; + fn try_conv_with(self, path_map: &PathMap) -> Result { + let path = path_map.get_path(self); + let url = Url::from_file_path(path) + .map_err(|()| format_err!("can't convert path to url: {}", path.display()))?; + Ok(url) + } +} + +impl<'a> TryConvWith for &'a TextDocumentItem { + type Ctx = PathMap; + type Output = FileId; + fn try_conv_with(self, path_map: &PathMap) -> Result { + self.uri.try_conv_with(path_map) + } +} + +impl<'a> TryConvWith for &'a VersionedTextDocumentIdentifier { + type Ctx = PathMap; + type Output = FileId; + fn try_conv_with(self, path_map: &PathMap) -> Result { + self.uri.try_conv_with(path_map) + } +} + +impl<'a> TryConvWith for &'a TextDocumentIdentifier { + type Ctx = PathMap; + type Output = FileId; + fn try_conv_with(self, path_map: &PathMap) -> Result { + self.uri.try_conv_with(path_map) + } +} + +pub fn to_location( + file_id: FileId, + range: TextRange, + path_map: &PathMap, + line_index: &LineIndex, +) -> Result { + let url = file_id.try_conv_with(path_map)?; + let loc = Location::new( + url, + range.conv_with(line_index), + ); + Ok(loc) +} pub trait MapConvWith<'a>: Sized { type Ctx; diff --git a/crates/server/src/main.rs b/crates/server/src/main.rs index 9c044d5a9f4..71d53199cb9 100644 --- a/crates/server/src/main.rs +++ b/crates/server/src/main.rs @@ -17,15 +17,16 @@ extern crate walkdir; extern crate libeditor; extern crate libanalysis; extern crate libsyntax2; +extern crate im; mod io; mod caps; mod req; mod dispatch; -mod util; mod conv; mod main_loop; mod vfs; +mod path_map; use threadpool::ThreadPool; use crossbeam_channel::bounded; @@ -33,7 +34,8 @@ use flexi_logger::{Logger, Duplicate}; use libanalysis::WorldState; use ::{ - io::{Io, RawMsg, RawResponse, RawRequest, RawNotification} + io::{Io, RawMsg, RawResponse, RawRequest, RawNotification}, + path_map::PathMap, }; pub type Result = ::std::result::Result; diff --git a/crates/server/src/main_loop/handlers.rs b/crates/server/src/main_loop/handlers.rs index 9de6f480bf1..637bf1b24f2 100644 --- a/crates/server/src/main_loop/handlers.rs +++ b/crates/server/src/main_loop/handlers.rs @@ -11,27 +11,29 @@ use libsyntax2::TextUnit; use serde_json::{to_value, from_value}; use ::{ + PathMap, req::{self, Decoration}, Result, - util::FilePath, - conv::{Conv, ConvWith, TryConvWith, MapConvWith}, + conv::{Conv, ConvWith, TryConvWith, MapConvWith, to_location}, }; pub fn handle_syntax_tree( world: World, + path_map: PathMap, params: req::SyntaxTreeParams, ) -> Result { - let path = params.text_document.file_path()?; - let file = world.file_syntax(&path)?; + let id = params.text_document.try_conv_with(&path_map)?; + let file = world.file_syntax(id)?; Ok(libeditor::syntax_tree(&file)) } pub fn handle_extend_selection( world: World, + path_map: PathMap, params: req::ExtendSelectionParams, ) -> Result { - let path = params.text_document.file_path()?; - let file = world.file_syntax(&path)?; - let line_index = world.file_line_index(&path)?; + let file_id = params.text_document.try_conv_with(&path_map)?; + let file = world.file_syntax(file_id)?; + let line_index = world.file_line_index(file_id)?; let selections = params.selections.into_iter() .map_conv_with(&line_index) .map(|r| libeditor::extend_selection(&file, r).unwrap_or(r)) @@ -42,11 +44,12 @@ pub fn handle_extend_selection( pub fn handle_document_symbol( world: World, + path_map: PathMap, params: req::DocumentSymbolParams, ) -> Result> { - let path = params.text_document.file_path()?; - let file = world.file_syntax(&path)?; - let line_index = world.file_line_index(&path)?; + let file_id = params.text_document.try_conv_with(&path_map)?; + let file = world.file_syntax(file_id)?; + let line_index = world.file_line_index(file_id)?; let mut parents: Vec<(DocumentSymbol, Option)> = Vec::new(); @@ -81,11 +84,12 @@ pub fn handle_document_symbol( pub fn handle_code_action( world: World, + path_map: PathMap, params: req::CodeActionParams, ) -> Result>> { - let path = params.text_document.file_path()?; - let file = world.file_syntax(&path)?; - let line_index = world.file_line_index(&path)?; + let file_id = params.text_document.try_conv_with(&path_map)?; + let file = world.file_syntax(file_id)?; + let line_index = world.file_line_index(file_id)?; let offset = params.range.conv_with(&line_index).start(); let mut ret = Vec::new(); @@ -105,6 +109,7 @@ pub fn handle_code_action( pub fn handle_workspace_symbol( world: World, + path_map: PathMap, params: req::WorkspaceSymbolParams, ) -> Result>> { let all_symbols = params.query.contains("#"); @@ -119,23 +124,26 @@ pub fn handle_workspace_symbol( q.limit(128); q }; - let mut res = exec_query(&world, query)?; + let mut res = exec_query(&world, &path_map, query)?; if res.is_empty() && !all_symbols { let mut query = Query::new(params.query); query.limit(128); - res = exec_query(&world, query)?; + res = exec_query(&world, &path_map, query)?; } return Ok(Some(res)); - fn exec_query(world: &World, query: Query) -> Result> { + fn exec_query(world: &World, path_map: &PathMap, query: Query) -> Result> { let mut res = Vec::new(); - for (path, symbol) in world.world_symbols(query) { - let line_index = world.file_line_index(path)?; + for (file_id, symbol) in world.world_symbols(query) { + let line_index = world.file_line_index(file_id)?; let info = SymbolInformation { name: symbol.name.to_string(), kind: symbol.kind.conv(), - location: (path, symbol.node_range).try_conv_with(&line_index)?, + location: to_location( + file_id, symbol.node_range, + path_map, &line_index + )?, container_name: None, }; res.push(info); @@ -146,15 +154,19 @@ pub fn handle_workspace_symbol( pub fn handle_goto_definition( world: World, + path_map: PathMap, params: req::TextDocumentPositionParams, ) -> Result> { - let path = params.text_document.file_path()?; - let line_index = world.file_line_index(&path)?; + let file_id = params.text_document.try_conv_with(&path_map)?; + let line_index = world.file_line_index(file_id)?; let offset = params.position.conv_with(&line_index); let mut res = Vec::new(); - for (path, symbol) in world.approximately_resolve_symbol(&path, offset)? { - let line_index = world.file_line_index(path)?; - let location = (path, symbol.node_range).try_conv_with(&line_index)?; + for (file_id, symbol) in world.approximately_resolve_symbol(file_id, offset)? { + let line_index = world.file_line_index(file_id)?; + let location = to_location( + file_id, symbol.node_range, + &path_map, &line_index, + )?; res.push(location) } Ok(Some(req::GotoDefinitionResponse::Array(res))) @@ -162,6 +174,7 @@ pub fn handle_goto_definition( pub fn handle_execute_command( world: World, + path_map: PathMap, mut params: req::ExecuteCommandParams, ) -> Result { if params.command.as_str() != "apply_code_action" { @@ -172,8 +185,8 @@ pub fn handle_execute_command( } let arg = params.arguments.pop().unwrap(); let arg: ActionRequest = from_value(arg)?; - let path = arg.text_document.file_path()?; - let file = world.file_syntax(&path)?; + let file_id = arg.text_document.try_conv_with(&path_map)?; + let file = world.file_syntax(file_id)?; let edit = match arg.id { ActionId::FlipComma => libeditor::flip_comma(&file, arg.offset).map(|edit| edit()), ActionId::AddDerive => libeditor::add_derive(&file, arg.offset).map(|edit| edit()), @@ -182,7 +195,7 @@ pub fn handle_execute_command( Some(edit) => edit, None => bail!("command not applicable"), }; - let line_index = world.file_line_index(&path)?; + let line_index = world.file_line_index(file_id)?; let mut changes = HashMap::new(); changes.insert( arg.text_document.uri, @@ -231,10 +244,14 @@ impl ActionId { } } -pub fn publish_diagnostics(world: World, uri: Url) -> Result { - let path = uri.file_path()?; - let file = world.file_syntax(&path)?; - let line_index = world.file_line_index(&path)?; +pub fn publish_diagnostics( + world: World, + path_map: PathMap, + uri: Url +) -> Result { + let file_id = uri.try_conv_with(&path_map)?; + let file = world.file_syntax(file_id)?; + let line_index = world.file_line_index(file_id)?; let diagnostics = libeditor::diagnostics(&file) .into_iter() .map(|d| Diagnostic { @@ -248,10 +265,14 @@ pub fn publish_diagnostics(world: World, uri: Url) -> Result Result { - let path = uri.file_path()?; - let file = world.file_syntax(&path)?; - let line_index = world.file_line_index(&path)?; +pub fn publish_decorations( + world: World, + path_map: PathMap, + uri: Url +) -> Result { + let file_id = uri.try_conv_with(&path_map)?; + let file = world.file_syntax(file_id)?; + let line_index = world.file_line_index(file_id)?; let decorations = libeditor::highlight(&file) .into_iter() .map(|h| Decoration { diff --git a/crates/server/src/main_loop/mod.rs b/crates/server/src/main_loop/mod.rs index bc898c17bd4..2a31297be21 100644 --- a/crates/server/src/main_loop/mod.rs +++ b/crates/server/src/main_loop/mod.rs @@ -1,22 +1,21 @@ mod handlers; use std::{ - path::PathBuf, collections::{HashSet, HashMap}, }; use threadpool::ThreadPool; use crossbeam_channel::{Sender, Receiver}; use languageserver_types::Url; -use libanalysis::{World, WorldState}; +use libanalysis::{World, WorldState, FileId}; use serde_json::to_value; use { req, dispatch, - Task, Result, + Task, Result, PathMap, io::{Io, RawMsg, RawRequest, RawNotification}, - util::FilePath, vfs::{FileEvent, FileEventKind}, + conv::TryConvWith, main_loop::handlers::{ handle_syntax_tree, handle_extend_selection, @@ -41,7 +40,8 @@ pub(super) fn main_loop( info!("server initialized, serving requests"); let mut next_request_id = 0; let mut pending_requests: HashSet = HashSet::new(); - let mut mem_map: HashMap> = HashMap::new(); + let mut path_map = PathMap::new(); + let mut mem_map: HashMap> = HashMap::new(); let mut fs_events_receiver = Some(&fs_events_receiver); loop { enum Event { @@ -98,12 +98,15 @@ pub(super) fn main_loop( }; (event.path, text) }) - .filter_map(|(path, text)| { - if mem_map.contains_key(path.as_path()) { - mem_map.insert(path, text); + .map(|(path, text)| { + (path_map.get_or_insert(path), text) + }) + .filter_map(|(id, text)| { + if mem_map.contains_key(&id) { + mem_map.insert(id, text); None } else { - Some((path, text)) + Some((id, text)) } }); @@ -112,12 +115,12 @@ pub(super) fn main_loop( Event::Msg(msg) => { match msg { RawMsg::Request(req) => { - if !on_request(io, world, pool, &task_sender, req)? { + if !on_request(io, world, &path_map, pool, &task_sender, req)? { return Ok(()); } } RawMsg::Notification(not) => { - on_notification(io, world, pool, &task_sender, not, &mut mem_map)? + on_notification(io, world, &mut path_map, pool, &task_sender, not, &mut mem_map)? } RawMsg::Response(resp) => { if !pending_requests.remove(&resp.id) { @@ -133,36 +136,38 @@ pub(super) fn main_loop( fn on_request( io: &mut Io, world: &WorldState, + path_map: &PathMap, pool: &ThreadPool, sender: &Sender, req: RawRequest, ) -> Result { let mut req = Some(req); handle_request_on_threadpool::( - &mut req, pool, world, sender, handle_syntax_tree, + &mut req, pool, path_map, world, sender, handle_syntax_tree, )?; handle_request_on_threadpool::( - &mut req, pool, world, sender, handle_extend_selection, + &mut req, pool, path_map, world, sender, handle_extend_selection, )?; handle_request_on_threadpool::( - &mut req, pool, world, sender, handle_document_symbol, + &mut req, pool, path_map, world, sender, handle_document_symbol, )?; handle_request_on_threadpool::( - &mut req, pool, world, sender, handle_code_action, + &mut req, pool, path_map, world, sender, handle_code_action, )?; handle_request_on_threadpool::( - &mut req, pool, world, sender, handle_workspace_symbol, + &mut req, pool, path_map, world, sender, handle_workspace_symbol, )?; handle_request_on_threadpool::( - &mut req, pool, world, sender, handle_goto_definition, + &mut req, pool, path_map, world, sender, handle_goto_definition, )?; dispatch::handle_request::(&mut req, |params, resp| { io.send(RawMsg::Response(resp.into_response(Ok(None))?)); let world = world.snapshot(); + let path_map = path_map.clone(); let sender = sender.clone(); pool.execute(move || { - let task = match handle_execute_command(world, params) { + let task = match handle_execute_command(world, path_map, params) { Ok(req) => match to_value(req) { Err(e) => Task::Die(e.into()), Ok(params) => { @@ -202,39 +207,43 @@ fn on_request( fn on_notification( io: &mut Io, world: &mut WorldState, + path_map: &mut PathMap, pool: &ThreadPool, sender: &Sender, not: RawNotification, - mem_map: &mut HashMap>, + mem_map: &mut HashMap>, ) -> Result<()> { let mut not = Some(not); dispatch::handle_notification::(&mut not, |params| { - let path = params.text_document.file_path()?; - mem_map.insert(path.clone(), None); - world.change_file(path, Some(params.text_document.text)); + let uri = params.text_document.uri; + let path = uri.to_file_path() + .map_err(|()| format_err!("invalid uri: {}", uri))?; + let file_id = path_map.get_or_insert(path); + mem_map.insert(file_id, None); + world.change_file(file_id, Some(params.text_document.text)); update_file_notifications_on_threadpool( - pool, world.snapshot(), sender.clone(), params.text_document.uri, + pool, world.snapshot(), path_map.clone(), sender.clone(), uri, ); Ok(()) })?; dispatch::handle_notification::(&mut not, |mut params| { - let path = params.text_document.file_path()?; + let file_id = params.text_document.try_conv_with(path_map)?; let text = params.content_changes.pop() .ok_or_else(|| format_err!("empty changes"))? .text; - world.change_file(path, Some(text)); + world.change_file(file_id, Some(text)); update_file_notifications_on_threadpool( - pool, world.snapshot(), sender.clone(), params.text_document.uri, + pool, world.snapshot(), path_map.clone(), sender.clone(), params.text_document.uri, ); Ok(()) })?; dispatch::handle_notification::(&mut not, |params| { - let path = params.text_document.file_path()?; - let text = match mem_map.remove(&path) { + let file_id = params.text_document.try_conv_with(path_map)?; + let text = match mem_map.remove(&file_id) { Some(text) => text, None => bail!("unmatched close notification"), }; - world.change_file(path, text); + world.change_file(file_id, text); let not = req::PublishDiagnosticsParams { uri: params.text_document.uri, diagnostics: Vec::new(), @@ -253,16 +262,18 @@ fn on_notification( fn handle_request_on_threadpool( req: &mut Option, pool: &ThreadPool, + path_map: &PathMap, world: &WorldState, sender: &Sender, - f: fn(World, R::Params) -> Result, + f: fn(World, PathMap, R::Params) -> Result, ) -> Result<()> { dispatch::handle_request::(req, |params, resp| { let world = world.snapshot(); + let path_map = path_map.clone(); let sender = sender.clone(); pool.execute(move || { - let res = f(world, params); + let res = f(world, path_map, params); let task = match resp.into_response(res) { Ok(resp) => Task::Respond(resp), Err(e) => Task::Die(e), @@ -276,11 +287,12 @@ fn handle_request_on_threadpool( fn update_file_notifications_on_threadpool( pool: &ThreadPool, world: World, + path_map: PathMap, sender: Sender, uri: Url, ) { pool.execute(move || { - match publish_diagnostics(world.clone(), uri.clone()) { + match publish_diagnostics(world.clone(), path_map.clone(), uri.clone()) { Err(e) => { error!("failed to compute diagnostics: {:?}", e) } @@ -289,7 +301,7 @@ fn update_file_notifications_on_threadpool( sender.send(Task::Notify(not)); } } - match publish_decorations(world, uri) { + match publish_decorations(world, path_map.clone(), uri) { Err(e) => { error!("failed to compute decorations: {:?}", e) } diff --git a/crates/server/src/path_map.rs b/crates/server/src/path_map.rs new file mode 100644 index 00000000000..2454ba05fed --- /dev/null +++ b/crates/server/src/path_map.rs @@ -0,0 +1,47 @@ +use std::path::{PathBuf, Path}; +use im; +use libanalysis::{FileId}; + +#[derive(Debug, Default, Clone)] +pub struct PathMap { + next_id: u32, + path2id: im::HashMap, + id2path: im::HashMap, +} + +impl PathMap { + pub fn new() -> PathMap { + Default::default() + } + + pub fn get_or_insert(&mut self, path: PathBuf) -> FileId { + self.path2id.get(path.as_path()) + .map(|&id| id) + .unwrap_or_else(|| { + let id = self.new_file_id(); + self.insert(path, id); + id + }) + } + + pub fn get_id(&self, path: &Path) -> Option { + self.path2id.get(path).map(|&id| id) + } + + pub fn get_path(&self, id: FileId) -> &Path { + self.id2path.get(&id) + .unwrap() + .as_path() + } + + fn insert(&mut self, path: PathBuf, id: FileId) { + self.path2id.insert(path.clone(), id); + self.id2path.insert(id, path.clone()); + } + + fn new_file_id(&mut self) -> FileId { + let id = FileId(self.next_id); + self.next_id += 1; + id + } +} diff --git a/crates/server/src/util.rs b/crates/server/src/util.rs deleted file mode 100644 index 6747c20a895..00000000000 --- a/crates/server/src/util.rs +++ /dev/null @@ -1,33 +0,0 @@ -use std::path::PathBuf; -use languageserver_types::{TextDocumentItem, VersionedTextDocumentIdentifier, - TextDocumentIdentifier, Url}; -use ::{Result}; - -pub trait FilePath { - fn file_path(&self) -> Result; -} - -impl FilePath for TextDocumentItem { - fn file_path(&self) -> Result { - self.uri.file_path() - } -} - -impl FilePath for VersionedTextDocumentIdentifier { - fn file_path(&self) -> Result { - self.uri.file_path() - } -} - -impl FilePath for TextDocumentIdentifier { - fn file_path(&self) -> Result { - self.uri.file_path() - } -} - -impl FilePath for Url { - fn file_path(&self) -> Result { - self.to_file_path() - .map_err(|()| format_err!("invalid uri: {}", self)) - } -}