From ed7ae78c6fd9e508f6e959c6a164cf8481f6b377 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Fri, 17 Aug 2018 19:54:08 +0300 Subject: ServerWorld --- code/src/extension.ts | 16 ++-- crates/libanalysis/src/lib.rs | 1 + crates/libeditor/src/code_actions.rs | 10 +-- crates/libeditor/tests/test.rs | 8 +- crates/server/src/conv.rs | 44 ++++++----- crates/server/src/main.rs | 5 +- crates/server/src/main_loop/handlers.rs | 102 ++++++++++++-------------- crates/server/src/main_loop/mod.rs | 125 ++++++++++---------------------- crates/server/src/server_world.rs | 117 ++++++++++++++++++++++++++++++ 9 files changed, 246 insertions(+), 182 deletions(-) create mode 100644 crates/server/src/server_world.rs diff --git a/code/src/extension.ts b/code/src/extension.ts index 95305db2d..084a9d769 100644 --- a/code/src/extension.ts +++ b/code/src/extension.ts @@ -60,14 +60,18 @@ export function activate(context: vscode.ExtensionContext) { vscode.workspace.onDidChangeTextDocument((event: vscode.TextDocumentChangeEvent) => { let doc = event.document if (doc.languageId != "rust") return - // We need to order this after LS updates, but there's no API for that. - // Hence, good old setTimeout. - setTimeout(() => { + afterLs(() => { textDocumentContentProvider.eventEmitter.fire(uris.syntaxTree) - }, 10) + }) }, null, context.subscriptions) } +// We need to order this after LS updates, but there's no API for that. +// Hence, good old setTimeout. +function afterLs(f) { + setTimeout(f, 10) +} + export function deactivate(): Thenable { if (!client) { return undefined; @@ -118,7 +122,9 @@ function startServer() { if (editor == null) return if (!editor.selection.isEmpty) return let position = client.protocol2CodeConverter.asPosition(params) - editor.selection = new vscode.Selection(position, position); + afterLs(() => { + editor.selection = new vscode.Selection(position, position) + }) } ) }) diff --git a/crates/libanalysis/src/lib.rs b/crates/libanalysis/src/lib.rs index a50a0f32f..b1093fe83 100644 --- a/crates/libanalysis/src/lib.rs +++ b/crates/libanalysis/src/lib.rs @@ -40,6 +40,7 @@ const INDEXING_THRESHOLD: usize = 128; pub type FileResolver = dyn Fn(FileId, &Path) -> Option + Send + Sync; +#[derive(Debug)] pub struct WorldState { data: Arc } diff --git a/crates/libeditor/src/code_actions.rs b/crates/libeditor/src/code_actions.rs index 6df64be12..4b2515835 100644 --- a/crates/libeditor/src/code_actions.rs +++ b/crates/libeditor/src/code_actions.rs @@ -71,13 +71,11 @@ fn non_trivia_sibling(node: SyntaxNodeRef, direction: Direction) -> Option Option { - find_leaf_at_offset(syntax, offset) - .find(|leaf| !leaf.kind().is_trivia()) -} - pub fn find_node<'a, N: AstNode<&'a SyntaxRoot>>(syntax: SyntaxNodeRef<'a>, offset: TextUnit) -> Option { - let leaf = find_non_trivia_leaf(syntax, offset)?; + let leaves = find_leaf_at_offset(syntax, offset); + let leaf = leaves.clone() + .find(|leaf| !leaf.kind().is_trivia()) + .or_else(|| leaves.right_biased())?; ancestors(leaf) .filter_map(N::cast) .next() diff --git a/crates/libeditor/tests/test.rs b/crates/libeditor/tests/test.rs index df4cb65d1..cc4226710 100644 --- a/crates/libeditor/tests/test.rs +++ b/crates/libeditor/tests/test.rs @@ -1,11 +1,8 @@ extern crate libeditor; extern crate libsyntax2; -extern crate itertools; #[macro_use] extern crate assert_eq_text; -use std::fmt; -use itertools::Itertools; use assert_eq_text::{assert_eq_dbg}; use libeditor::{ File, TextUnit, TextRange, ActionResult, CursorPosition, @@ -118,6 +115,11 @@ fn test_add_derive() { "#[derive(<|>)]\nstruct Foo { a: i32, }", |file, off| add_derive(file, off).map(|f| f()), ); + check_action( + "struct Foo { <|> a: i32, }", + "#[derive(<|>)]\nstruct Foo { a: i32, }", + |file, off| add_derive(file, off).map(|f| f()), + ); check_action( "#[derive(Clone)]\nstruct Foo { a: i32<|>, }", "#[derive(Clone<|>)]\nstruct Foo { a: i32, }", diff --git a/crates/server/src/conv.rs b/crates/server/src/conv.rs index b3709ccaf..fc9056914 100644 --- a/crates/server/src/conv.rs +++ b/crates/server/src/conv.rs @@ -6,7 +6,10 @@ use libeditor::{LineIndex, LineCol, Edit, AtomEdit}; use libsyntax2::{SyntaxKind, TextUnit, TextRange}; use libanalysis::FileId; -use {Result, PathMap}; +use { + Result, + server_world::ServerWorld, +}; pub trait Conv { type Output; @@ -126,57 +129,52 @@ impl ConvWith for Option { } impl<'a> TryConvWith for &'a Url { - type Ctx = PathMap; + type Ctx = ServerWorld; 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())) + fn try_conv_with(self, world: &ServerWorld) -> Result { + world.uri_to_file_id(self) } } impl TryConvWith for FileId { - type Ctx = PathMap; + type Ctx = ServerWorld; 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) + fn try_conv_with(self, world: &ServerWorld) -> Result { + world.file_id_to_uri(self) } } impl<'a> TryConvWith for &'a TextDocumentItem { - type Ctx = PathMap; + type Ctx = ServerWorld; type Output = FileId; - fn try_conv_with(self, path_map: &PathMap) -> Result { - self.uri.try_conv_with(path_map) + fn try_conv_with(self, world: &ServerWorld) -> Result { + self.uri.try_conv_with(world) } } impl<'a> TryConvWith for &'a VersionedTextDocumentIdentifier { - type Ctx = PathMap; + type Ctx = ServerWorld; type Output = FileId; - fn try_conv_with(self, path_map: &PathMap) -> Result { - self.uri.try_conv_with(path_map) + fn try_conv_with(self, world: &ServerWorld) -> Result { + self.uri.try_conv_with(world) } } impl<'a> TryConvWith for &'a TextDocumentIdentifier { - type Ctx = PathMap; + type Ctx = ServerWorld; type Output = FileId; - fn try_conv_with(self, path_map: &PathMap) -> Result { - self.uri.try_conv_with(path_map) + fn try_conv_with(self, world: &ServerWorld) -> Result { + world.uri_to_file_id(&self.uri) } } pub fn to_location( file_id: FileId, range: TextRange, - path_map: &PathMap, + world: &ServerWorld, line_index: &LineIndex, ) -> Result { - let url = file_id.try_conv_with(path_map)?; + let url = file_id.try_conv_with(world)?; let loc = Location::new( url, range.conv_with(line_index), diff --git a/crates/server/src/main.rs b/crates/server/src/main.rs index 71d53199c..5e4c0fe7e 100644 --- a/crates/server/src/main.rs +++ b/crates/server/src/main.rs @@ -27,15 +27,14 @@ mod conv; mod main_loop; mod vfs; mod path_map; +mod server_world; use threadpool::ThreadPool; use crossbeam_channel::bounded; use flexi_logger::{Logger, Duplicate}; -use libanalysis::WorldState; use ::{ io::{Io, RawMsg, RawResponse, RawRequest, RawNotification}, - path_map::PathMap, }; pub type Result = ::std::result::Result; @@ -116,7 +115,6 @@ enum Task { fn initialized(io: &mut Io) -> Result<()> { { - let mut world = WorldState::new(); let mut pool = ThreadPool::new(4); let (task_sender, task_receiver) = bounded::(16); let (fs_events_receiver, watcher) = vfs::watch(vec![ @@ -125,7 +123,6 @@ fn initialized(io: &mut Io) -> Result<()> { info!("lifecycle: handshake finished, server ready to serve requests"); let res = main_loop::main_loop( io, - &mut world, &mut pool, task_sender, task_receiver.clone(), diff --git a/crates/server/src/main_loop/handlers.rs b/crates/server/src/main_loop/handlers.rs index f25c64c97..675f69bec 100644 --- a/crates/server/src/main_loop/handlers.rs +++ b/crates/server/src/main_loop/handlers.rs @@ -5,35 +5,33 @@ use languageserver_types::{ Command, TextDocumentIdentifier, WorkspaceEdit, SymbolInformation, Position, }; -use libanalysis::{World, Query}; +use libanalysis::{Query}; use libeditor::{self, CursorPosition}; use libsyntax2::TextUnit; use serde_json::{to_value, from_value}; use ::{ - PathMap, req::{self, Decoration}, Result, conv::{Conv, ConvWith, TryConvWith, MapConvWith, to_location}, + server_world::ServerWorld, }; pub fn handle_syntax_tree( - world: World, - path_map: PathMap, + world: ServerWorld, params: req::SyntaxTreeParams, ) -> Result { - let id = params.text_document.try_conv_with(&path_map)?; - let file = world.file_syntax(id)?; + let id = params.text_document.try_conv_with(&world)?; + let file = world.analysis().file_syntax(id)?; Ok(libeditor::syntax_tree(&file)) } pub fn handle_extend_selection( - world: World, - path_map: PathMap, + world: ServerWorld, params: req::ExtendSelectionParams, ) -> Result { - 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 file_id = params.text_document.try_conv_with(&world)?; + let file = world.analysis().file_syntax(file_id)?; + let line_index = world.analysis().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)) @@ -43,13 +41,12 @@ pub fn handle_extend_selection( } pub fn handle_find_matching_brace( - world: World, - path_map: PathMap, + world: ServerWorld, params: req::FindMatchingBraceParams, ) -> Result> { - 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 file_id = params.text_document.try_conv_with(&world)?; + let file = world.analysis().file_syntax(file_id)?; + let line_index = world.analysis().file_line_index(file_id)?; let res = params.offsets .into_iter() .map_conv_with(&line_index) @@ -62,13 +59,12 @@ pub fn handle_find_matching_brace( } pub fn handle_document_symbol( - world: World, - path_map: PathMap, + world: ServerWorld, params: req::DocumentSymbolParams, ) -> Result> { - 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 file_id = params.text_document.try_conv_with(&world)?; + let file = world.analysis().file_syntax(file_id)?; + let line_index = world.analysis().file_line_index(file_id)?; let mut parents: Vec<(DocumentSymbol, Option)> = Vec::new(); @@ -102,13 +98,12 @@ pub fn handle_document_symbol( } pub fn handle_code_action( - world: World, - path_map: PathMap, + world: ServerWorld, params: req::CodeActionParams, ) -> Result>> { - 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 file_id = params.text_document.try_conv_with(&world)?; + let file = world.analysis().file_syntax(file_id)?; + let line_index = world.analysis().file_line_index(file_id)?; let offset = params.range.conv_with(&line_index).start(); let mut ret = Vec::new(); @@ -127,8 +122,7 @@ pub fn handle_code_action( } pub fn handle_workspace_symbol( - world: World, - path_map: PathMap, + world: ServerWorld, params: req::WorkspaceSymbolParams, ) -> Result>> { let all_symbols = params.query.contains("#"); @@ -143,25 +137,25 @@ pub fn handle_workspace_symbol( q.limit(128); q }; - let mut res = exec_query(&world, &path_map, query)?; + let mut res = exec_query(&world, query)?; if res.is_empty() && !all_symbols { let mut query = Query::new(params.query); query.limit(128); - res = exec_query(&world, &path_map, query)?; + res = exec_query(&world, query)?; } return Ok(Some(res)); - fn exec_query(world: &World, path_map: &PathMap, query: Query) -> Result> { + fn exec_query(world: &ServerWorld, query: Query) -> Result> { let mut res = Vec::new(); - for (file_id, symbol) in world.world_symbols(query) { - let line_index = world.file_line_index(file_id)?; + for (file_id, symbol) in world.analysis().world_symbols(query) { + let line_index = world.analysis().file_line_index(file_id)?; let info = SymbolInformation { name: symbol.name.to_string(), kind: symbol.kind.conv(), location: to_location( file_id, symbol.node_range, - path_map, &line_index + world, &line_index )?, container_name: None, }; @@ -172,19 +166,18 @@ pub fn handle_workspace_symbol( } pub fn handle_goto_definition( - world: World, - path_map: PathMap, + world: ServerWorld, params: req::TextDocumentPositionParams, ) -> Result> { - let file_id = params.text_document.try_conv_with(&path_map)?; - let line_index = world.file_line_index(file_id)?; + let file_id = params.text_document.try_conv_with(&world)?; + let line_index = world.analysis().file_line_index(file_id)?; let offset = params.position.conv_with(&line_index); let mut res = Vec::new(); - for (file_id, symbol) in world.approximately_resolve_symbol(file_id, offset)? { - let line_index = world.file_line_index(file_id)?; + for (file_id, symbol) in world.analysis().approximately_resolve_symbol(file_id, offset)? { + let line_index = world.analysis().file_line_index(file_id)?; let location = to_location( file_id, symbol.node_range, - &path_map, &line_index, + &world, &line_index, )?; res.push(location) } @@ -192,8 +185,7 @@ pub fn handle_goto_definition( } pub fn handle_execute_command( - world: World, - path_map: PathMap, + world: ServerWorld, mut params: req::ExecuteCommandParams, ) -> Result<(req::ApplyWorkspaceEditParams, Option)> { if params.command.as_str() != "apply_code_action" { @@ -204,13 +196,13 @@ pub fn handle_execute_command( } let arg = params.arguments.pop().unwrap(); let arg: ActionRequest = from_value(arg)?; - let file_id = arg.text_document.try_conv_with(&path_map)?; - let file = world.file_syntax(file_id)?; + let file_id = arg.text_document.try_conv_with(&world)?; + let file = world.analysis().file_syntax(file_id)?; let action_result = match arg.id { ActionId::FlipComma => libeditor::flip_comma(&file, arg.offset).map(|f| f()), ActionId::AddDerive => libeditor::add_derive(&file, arg.offset).map(|f| f()), }.ok_or_else(|| format_err!("command not applicable"))?; - let line_index = world.file_line_index(file_id)?; + let line_index = world.analysis().file_line_index(file_id)?; let mut changes = HashMap::new(); changes.insert( arg.text_document.uri, @@ -265,13 +257,12 @@ impl ActionId { } pub fn publish_diagnostics( - world: World, - path_map: PathMap, + world: ServerWorld, 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 file_id = world.uri_to_file_id(&uri)?; + let file = world.analysis().file_syntax(file_id)?; + let line_index = world.analysis().file_line_index(file_id)?; let diagnostics = libeditor::diagnostics(&file) .into_iter() .map(|d| Diagnostic { @@ -286,13 +277,12 @@ pub fn publish_diagnostics( } pub fn publish_decorations( - world: World, - path_map: PathMap, + world: ServerWorld, 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 file_id = world.uri_to_file_id(&uri)?; + let file = world.analysis().file_syntax(file_id)?; + let line_index = world.analysis().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 a8340df59..ad7c480dc 100644 --- a/crates/server/src/main_loop/mod.rs +++ b/crates/server/src/main_loop/mod.rs @@ -1,21 +1,20 @@ mod handlers; use std::{ - collections::{HashSet, HashMap}, + collections::{HashSet}, }; use threadpool::ThreadPool; use crossbeam_channel::{Sender, Receiver}; use languageserver_types::Url; -use libanalysis::{World, WorldState, FileId}; use serde_json::to_value; use { req, dispatch, - Task, Result, PathMap, + Task, Result, io::{Io, RawMsg, RawRequest, RawNotification}, - vfs::{FileEvent, FileEventKind}, - conv::TryConvWith, + vfs::FileEvent, + server_world::{ServerWorldState, ServerWorld}, main_loop::handlers::{ handle_syntax_tree, handle_extend_selection, @@ -32,17 +31,16 @@ use { pub(super) fn main_loop( io: &mut Io, - world: &mut WorldState, pool: &mut ThreadPool, task_sender: Sender, task_receiver: Receiver, fs_events_receiver: Receiver>, ) -> Result<()> { info!("server initialized, serving requests"); + let mut state = ServerWorldState::new(); + let mut next_request_id = 0; let mut pending_requests: HashSet = HashSet::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 { @@ -91,37 +89,17 @@ pub(super) fn main_loop( } Event::Fs(events) => { trace!("fs change, {} events", events.len()); - let changes = events.into_iter() - .map(|event| { - let text = match event.kind { - FileEventKind::Add(text) => Some(text), - FileEventKind::Remove => None, - }; - (event.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((id, text)) - } - }); - - world.change_files(changes); + state.apply_fs_changes(events); } Event::Msg(msg) => { match msg { RawMsg::Request(req) => { - if !on_request(io, world, &path_map, pool, &task_sender, req)? { + if !on_request(io, &state, pool, &task_sender, req)? { return Ok(()); } } RawMsg::Notification(not) => { - on_notification(io, world, &mut path_map, pool, &task_sender, not, &mut mem_map)? + on_notification(io, &mut state, pool, &task_sender, not)? } RawMsg::Response(resp) => { if !pending_requests.remove(&resp.id) { @@ -136,45 +114,40 @@ pub(super) fn main_loop( fn on_request( io: &mut Io, - world: &WorldState, - path_map: &PathMap, + world: &ServerWorldState, pool: &ThreadPool, sender: &Sender, req: RawRequest, ) -> Result { let mut req = Some(req); handle_request_on_threadpool::( - &mut req, pool, path_map, world, sender, handle_syntax_tree, + &mut req, pool, world, sender, handle_syntax_tree, )?; handle_request_on_threadpool::( - &mut req, pool, path_map, world, sender, handle_extend_selection, + &mut req, pool, world, sender, handle_extend_selection, )?; handle_request_on_threadpool::( - &mut req, pool, path_map, world, sender, handle_find_matching_brace, + &mut req, pool, world, sender, handle_find_matching_brace, )?; handle_request_on_threadpool::( - &mut req, pool, path_map, world, sender, handle_document_symbol, + &mut req, pool, world, sender, handle_document_symbol, )?; handle_request_on_threadpool::( - &mut req, pool, path_map, world, sender, handle_code_action, + &mut req, pool, world, sender, handle_code_action, )?; handle_request_on_threadpool::( - &mut req, pool, path_map, world, sender, handle_workspace_symbol, + &mut req, pool, world, sender, handle_workspace_symbol, )?; handle_request_on_threadpool::( - &mut req, pool, path_map, world, sender, handle_goto_definition, + &mut req, pool, 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 pm = path_map.clone(); - move |id, path| pm.resolve(id, path) - }); - let path_map = path_map.clone(); + let world = world.snapshot(); let sender = sender.clone(); pool.execute(move || { - let (edit, cursor) = match handle_execute_command(world, path_map, params) { + let (edit, cursor) = match handle_execute_command(world, params) { Ok(res) => res, Err(e) => return sender.send(Task::Die(e)), }; @@ -221,60 +194,48 @@ fn on_request( fn on_notification( io: &mut Io, - world: &mut WorldState, - path_map: &mut PathMap, + state: &mut ServerWorldState, pool: &ThreadPool, sender: &Sender, not: RawNotification, - mem_map: &mut HashMap>, ) -> Result<()> { let mut not = Some(not); dispatch::handle_notification::(&mut not, |params| { 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)); + state.add_mem_file(path, params.text_document.text); update_file_notifications_on_threadpool( pool, - world.snapshot({ - let pm = path_map.clone(); - move |id, path| pm.resolve(id, path) - }), - path_map.clone(), + state.snapshot(), sender.clone(), uri, ); Ok(()) })?; dispatch::handle_notification::(&mut not, |mut params| { - let file_id = params.text_document.try_conv_with(path_map)?; + let uri = params.text_document.uri; + let path = uri.to_file_path() + .map_err(|()| format_err!("invalid uri: {}", uri))?; let text = params.content_changes.pop() .ok_or_else(|| format_err!("empty changes"))? .text; - world.change_file(file_id, Some(text)); + state.change_mem_file(path.as_path(), text)?; update_file_notifications_on_threadpool( pool, - world.snapshot({ - let pm = path_map.clone(); - move |id, path| pm.resolve(id, path) - }), - path_map.clone(), + state.snapshot(), sender.clone(), - params.text_document.uri, + uri, ); Ok(()) })?; dispatch::handle_notification::(&mut not, |params| { - 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(file_id, text); + let uri = params.text_document.uri; + let path = uri.to_file_path() + .map_err(|()| format_err!("invalid uri: {}", uri))?; + state.remove_mem_file(path.as_path())?; let not = req::PublishDiagnosticsParams { - uri: params.text_document.uri, + uri, diagnostics: Vec::new(), }; let not = dispatch::send_notification::(not); @@ -291,21 +252,16 @@ fn on_notification( fn handle_request_on_threadpool( req: &mut Option, pool: &ThreadPool, - path_map: &PathMap, - world: &WorldState, + world: &ServerWorldState, sender: &Sender, - f: fn(World, PathMap, R::Params) -> Result, + f: fn(ServerWorld, R::Params) -> Result, ) -> Result<()> { dispatch::handle_request::(req, |params, resp| { - let world = world.snapshot({ - let pm = path_map.clone(); - move |id, path| pm.resolve(id, path) - }); - let path_map = path_map.clone(); + let world = world.snapshot(); let sender = sender.clone(); pool.execute(move || { - let res = f(world, path_map, params); + let res = f(world, params); let task = match resp.into_response(res) { Ok(resp) => Task::Respond(resp), Err(e) => Task::Die(e), @@ -318,13 +274,12 @@ fn handle_request_on_threadpool( fn update_file_notifications_on_threadpool( pool: &ThreadPool, - world: World, - path_map: PathMap, + world: ServerWorld, sender: Sender, uri: Url, ) { pool.execute(move || { - match publish_diagnostics(world.clone(), path_map.clone(), uri.clone()) { + match publish_diagnostics(world.clone(), uri.clone()) { Err(e) => { error!("failed to compute diagnostics: {:?}", e) } @@ -333,7 +288,7 @@ fn update_file_notifications_on_threadpool( sender.send(Task::Notify(not)); } } - match publish_decorations(world, path_map.clone(), uri) { + match publish_decorations(world, uri) { Err(e) => { error!("failed to compute decorations: {:?}", e) } diff --git a/crates/server/src/server_world.rs b/crates/server/src/server_world.rs new file mode 100644 index 000000000..c0d2efb86 --- /dev/null +++ b/crates/server/src/server_world.rs @@ -0,0 +1,117 @@ +use std::{ + path::{PathBuf, Path}, + collections::HashMap, +}; + +use languageserver_types::Url; +use libanalysis::{FileId, WorldState, World}; + +use { + Result, + path_map::PathMap, + vfs::{FileEvent, FileEventKind}, +}; + +#[derive(Debug)] +pub struct ServerWorldState { + pub analysis: WorldState, + pub path_map: PathMap, + pub mem_map: HashMap>, +} + +#[derive(Clone)] +pub struct ServerWorld { + pub analysis: World, + pub path_map: PathMap, +} + +impl ServerWorldState { + pub fn new() -> ServerWorldState { + ServerWorldState { + analysis: WorldState::new(), + path_map: PathMap::new(), + mem_map: HashMap::new(), + } + } + + pub fn apply_fs_changes(&mut self, events: Vec) { + let pm = &mut self.path_map; + let mm = &mut self.mem_map; + let changes = events.into_iter() + .map(|event| { + let text = match event.kind { + FileEventKind::Add(text) => Some(text), + FileEventKind::Remove => None, + }; + (event.path, text) + }) + .map(|(path, text)| { + (pm.get_or_insert(path), text) + }) + .filter_map(|(id, text)| { + if mm.contains_key(&id) { + mm.insert(id, text); + None + } else { + Some((id, text)) + } + }); + + self.analysis.change_files(changes); + } + + pub fn add_mem_file(&mut self, path: PathBuf, text: String) { + let file_id = self.path_map.get_or_insert(path); + self.mem_map.insert(file_id, None); + self.analysis.change_file(file_id, Some(text)); + } + + pub fn change_mem_file(&mut self, path: &Path, text: String) -> Result<()> { + let file_id = self.path_map.get_id(path).ok_or_else(|| { + format_err!("change to unknown file: {}", path.display()) + })?; + self.analysis.change_file(file_id, Some(text)); + Ok(()) + } + + pub fn remove_mem_file(&mut self, path: &Path) -> Result<()> { + let file_id = self.path_map.get_id(path).ok_or_else(|| { + format_err!("change to unknown file: {}", path.display()) + })?; + let text = match self.mem_map.remove(&file_id) { + Some(text) => text, + None => bail!("unmatched close notification"), + }; + self.analysis.change_file(file_id, text); + Ok(()) + } + + pub fn snapshot(&self) -> ServerWorld { + let pm = self.path_map.clone(); + ServerWorld { + analysis: self.analysis.snapshot(move |id, path| { + pm.resolve(id, path) + }), + path_map: self.path_map.clone() + } + } +} + +impl ServerWorld { + pub fn analysis(&self) -> &World { + &self.analysis + } + + pub fn uri_to_file_id(&self, uri: &Url) -> Result { + let path = uri.to_file_path() + .map_err(|()| format_err!("invalid uri: {}", uri))?; + self.path_map.get_id(&path).ok_or_else(|| format_err!("unknown file: {}", path.display())) + } + + pub fn file_id_to_uri(&self, id: FileId) -> Result { + let path = self.path_map.get_path(id); + let url = Url::from_file_path(path) + .map_err(|()| format_err!("can't convert path to url: {}", path.display()))?; + Ok(url) + } +} -- cgit v1.2.3