From 8abf5363433e977c5393bb569e2a5d559cb0a602 Mon Sep 17 00:00:00 2001
From: Aleksey Kladov <aleksey.kladov@gmail.com>
Date: Wed, 29 Aug 2018 18:03:14 +0300
Subject: Grand refactoring

---
 crates/server/src/main_loop/handlers.rs | 384 +++++++++++---------------------
 crates/server/src/main_loop/mod.rs      | 169 ++++----------
 2 files changed, 179 insertions(+), 374 deletions(-)

(limited to 'crates/server/src/main_loop')

diff --git a/crates/server/src/main_loop/handlers.rs b/crates/server/src/main_loop/handlers.rs
index 3ee0873f4..45083b084 100644
--- a/crates/server/src/main_loop/handlers.rs
+++ b/crates/server/src/main_loop/handlers.rs
@@ -2,16 +2,13 @@ use std::collections::HashMap;
 
 use languageserver_types::{
     Diagnostic, DiagnosticSeverity, Url, DocumentSymbol,
-    Command, TextDocumentIdentifier, WorkspaceEdit,
+    Command, TextDocumentIdentifier,
     SymbolInformation, Position, Location, TextEdit,
     CompletionItem, InsertTextFormat, CompletionItemKind,
 };
-use serde_json::{to_value, from_value};
-use url_serde;
-use libanalysis::{self, Query, FileId};
-use libeditor;
+use serde_json::to_value;
+use libanalysis::{Query, FileId, RunnableKind};
 use libsyntax2::{
-    TextUnit,
     text_utils::contains_offset_nonstrict,
 };
 
@@ -26,8 +23,8 @@ pub fn handle_syntax_tree(
     params: req::SyntaxTreeParams,
 ) -> Result<String> {
     let id = params.text_document.try_conv_with(&world)?;
-    let file = world.analysis().file_syntax(id)?;
-    Ok(libeditor::syntax_tree(&file))
+    let res = world.analysis().syntax_tree(id);
+    Ok(res)
 }
 
 pub fn handle_extend_selection(
@@ -35,11 +32,11 @@ pub fn handle_extend_selection(
     params: req::ExtendSelectionParams,
 ) -> Result<req::ExtendSelectionResult> {
     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 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))
+        .map(|r| world.analysis().extend_selection(&file, r))
         .map_conv_with(&line_index)
         .collect();
     Ok(req::ExtendSelectionResult { selections })
@@ -50,13 +47,13 @@ pub fn handle_find_matching_brace(
     params: req::FindMatchingBraceParams,
 ) -> Result<Vec<Position>> {
     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 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)
         .map(|offset| {
-            libeditor::matching_brace(&file, offset).unwrap_or(offset)
+            world.analysis().matching_brace(&file, offset).unwrap_or(offset)
         })
         .map_conv_with(&line_index)
         .collect();
@@ -66,13 +63,31 @@ pub fn handle_find_matching_brace(
 pub fn handle_join_lines(
     world: ServerWorld,
     params: req::JoinLinesParams,
-) -> Result<Vec<TextEdit>> {
+) -> Result<req::SourceChange> {
     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 line_index = world.analysis().file_line_index(file_id);
     let range = params.range.conv_with(&line_index);
-    let res = libeditor::join_lines(&file, range);
-    Ok(res.edit.conv_with(&line_index))
+    world.analysis().join_lines(file_id, range)
+        .try_conv_with(&world)
+}
+
+pub fn handle_on_type_formatting(
+    world: ServerWorld,
+    params: req::DocumentOnTypeFormattingParams,
+) -> Result<Option<Vec<TextEdit>>> {
+    if params.ch != "=" {
+        return Ok(None);
+    }
+
+    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 edits = match world.analysis().on_eq_typed(file_id, offset) {
+        None => return Ok(None),
+        Some(mut action) => action.source_file_edits.pop().unwrap().edits,
+    };
+    let edits = edits.into_iter().map_conv_with(&line_index).collect();
+    Ok(Some(edits))
 }
 
 pub fn handle_document_symbol(
@@ -80,12 +95,11 @@ pub fn handle_document_symbol(
     params: req::DocumentSymbolParams,
 ) -> Result<Option<req::DocumentSymbolResponse>> {
     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 line_index = world.analysis().file_line_index(file_id);
 
     let mut parents: Vec<(DocumentSymbol, Option<usize>)> = Vec::new();
 
-    for symbol in libeditor::file_structure(&file) {
+    for symbol in world.analysis().file_structure(file_id) {
         let doc_symbol = DocumentSymbol {
             name: symbol.label,
             detail: Some("".to_string()),
@@ -114,130 +128,6 @@ pub fn handle_document_symbol(
     Ok(Some(req::DocumentSymbolResponse::Nested(res)))
 }
 
-pub fn handle_code_action(
-    world: ServerWorld,
-    params: req::CodeActionParams,
-) -> Result<Option<Vec<Command>>> {
-    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 res = Vec::new();
-
-    let actions = &[
-        (ActionId::FlipComma, libeditor::flip_comma(&file, offset).is_some()),
-        (ActionId::AddDerive, libeditor::add_derive(&file, offset).is_some()),
-        (ActionId::AddImpl, libeditor::add_impl(&file, offset).is_some()),
-    ];
-
-    for (id, edit) in actions {
-        if *edit {
-            let cmd = apply_code_action_cmd(*id, params.text_document.clone(), offset);
-            res.push(cmd);
-        }
-    }
-
-    for (diag, quick_fix) in world.analysis().diagnostics(file_id)? {
-        let quick_fix = match quick_fix {
-            Some(quick_fix) => quick_fix,
-            None => continue,
-        };
-        if !contains_offset_nonstrict(diag.range, offset) {
-            continue;
-        }
-        let mut ops = Vec::new();
-        for op in quick_fix.fs_ops {
-            let op = match op {
-                libanalysis::FsOp::CreateFile { anchor, path } => {
-                    let uri = world.file_id_to_uri(anchor)?;
-                    let path = &path.as_str()[3..]; // strip `../` b/c url is weird
-                    let uri = uri.join(path)?;
-                    FsOp::CreateFile { uri }
-                },
-                libanalysis::FsOp::MoveFile { file, path } => {
-                    let src = world.file_id_to_uri(file)?;
-                    let path = &path.as_str()[3..]; // strip `../` b/c url is weird
-                    let dst = src.join(path)?;
-                    FsOp::MoveFile { src, dst }
-                },
-            };
-            ops.push(op)
-        }
-        let cmd = Command {
-            title: "Create module".to_string(),
-            command: "libsyntax-rust.fsEdit".to_string(),
-            arguments: Some(vec![to_value(ops).unwrap()]),
-        };
-        res.push(cmd)
-    }
-    return Ok(Some(res));
-}
-
-#[derive(Serialize)]
-#[serde(tag = "type", rename_all = "camelCase")]
-enum FsOp {
-    CreateFile {
-        #[serde(with = "url_serde")]
-        uri: Url
-    },
-    MoveFile {
-        #[serde(with = "url_serde")]
-        src: Url,
-        #[serde(with = "url_serde")]
-        dst: Url,
-    }
-}
-
-pub fn handle_runnables(
-    world: ServerWorld,
-    params: req::RunnablesParams,
-) -> Result<Vec<req::Runnable>> {
-    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.position.map(|it| it.conv_with(&line_index));
-    let mut res = Vec::new();
-    for runnable in libeditor::runnables(&file) {
-        if let Some(offset) = offset {
-            if !contains_offset_nonstrict(runnable.range, offset) {
-                continue;
-            }
-        }
-
-        let r = req::Runnable {
-            range: runnable.range.conv_with(&line_index),
-            label: match &runnable.kind {
-                libeditor::RunnableKind::Test { name } =>
-                    format!("test {}", name),
-                libeditor::RunnableKind::Bin =>
-                    "run binary".to_string(),
-            },
-            bin: "cargo".to_string(),
-            args: match runnable.kind {
-                libeditor::RunnableKind::Test { name } => {
-                    vec![
-                        "test".to_string(),
-                        "--".to_string(),
-                        name,
-                        "--nocapture".to_string(),
-                    ]
-                }
-                libeditor::RunnableKind::Bin => vec!["run".to_string()]
-            },
-            env: {
-                let mut m = HashMap::new();
-                m.insert(
-                    "RUST_BACKTRACE".to_string(),
-                    "short".to_string(),
-                );
-                m
-            }
-        };
-        res.push(r);
-    }
-    return Ok(res);
-}
-
 pub fn handle_workspace_symbol(
     world: ServerWorld,
     params: req::WorkspaceSymbolParams,
@@ -265,8 +155,8 @@ pub fn handle_workspace_symbol(
 
     fn exec_query(world: &ServerWorld, query: Query) -> Result<Vec<SymbolInformation>> {
         let mut res = Vec::new();
-        for (file_id, symbol) in world.analysis().world_symbols(query) {
-            let line_index = world.analysis().file_line_index(file_id)?;
+        for (file_id, symbol) in world.analysis().symbol_search(query) {
+            let line_index = world.analysis().file_line_index(file_id);
             let info = SymbolInformation {
                 name: symbol.name.to_string(),
                 kind: symbol.kind.conv(),
@@ -287,11 +177,11 @@ pub fn handle_goto_definition(
     params: req::TextDocumentPositionParams,
 ) -> Result<Option<req::GotoDefinitionResponse>> {
     let file_id = params.text_document.try_conv_with(&world)?;
-    let line_index = world.analysis().file_line_index(file_id)?;
+    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.analysis().approximately_resolve_symbol(file_id, offset)? {
-        let line_index = world.analysis().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,
             &world, &line_index,
@@ -308,7 +198,7 @@ pub fn handle_parent_module(
     let file_id = params.try_conv_with(&world)?;
     let mut res = Vec::new();
     for (file_id, symbol) in world.analysis().parent_module(file_id) {
-        let line_index = world.analysis().file_line_index(file_id)?;
+        let line_index = world.analysis().file_line_index(file_id);
         let location = to_location(
             file_id, symbol.node_range,
             &world, &line_index
@@ -318,15 +208,71 @@ pub fn handle_parent_module(
     Ok(res)
 }
 
+pub fn handle_runnables(
+    world: ServerWorld,
+    params: req::RunnablesParams,
+) -> Result<Vec<req::Runnable>> {
+    let file_id = params.text_document.try_conv_with(&world)?;
+    let line_index = world.analysis().file_line_index(file_id);
+    let offset = params.position.map(|it| it.conv_with(&line_index));
+    let mut res = Vec::new();
+    for runnable in world.analysis().runnables(file_id) {
+        if let Some(offset) = offset {
+            if !contains_offset_nonstrict(runnable.range, offset) {
+                continue;
+            }
+        }
+
+        let r = req::Runnable {
+            range: runnable.range.conv_with(&line_index),
+            label: match &runnable.kind {
+                RunnableKind::Test { name } =>
+                    format!("test {}", name),
+                RunnableKind::Bin =>
+                    "run binary".to_string(),
+            },
+            bin: "cargo".to_string(),
+            args: match runnable.kind {
+                RunnableKind::Test { name } => {
+                    vec![
+                        "test".to_string(),
+                        "--".to_string(),
+                        name,
+                        "--nocapture".to_string(),
+                    ]
+                }
+                RunnableKind::Bin => vec!["run".to_string()]
+            },
+            env: {
+                let mut m = HashMap::new();
+                m.insert(
+                    "RUST_BACKTRACE".to_string(),
+                    "short".to_string(),
+                );
+                m
+            }
+        };
+        res.push(r);
+    }
+    return Ok(res);
+}
+
+pub fn handle_decorations(
+    world: ServerWorld,
+    params: TextDocumentIdentifier,
+) -> Result<Vec<Decoration>> {
+    let file_id = params.try_conv_with(&world)?;
+    Ok(highlight(&world, file_id))
+}
+
 pub fn handle_completion(
     world: ServerWorld,
     params: req::CompletionParams,
 ) -> Result<Option<req::CompletionResponse>> {
     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 line_index = world.analysis().file_line_index(file_id);
     let offset = params.position.conv_with(&line_index);
-    let items = match libeditor::scope_completion(&file, offset) {
+    let items = match world.analysis().completions(file_id, offset) {
         None => return Ok(None),
         Some(items) => items,
     };
@@ -348,91 +294,33 @@ pub fn handle_completion(
     Ok(Some(req::CompletionResponse::Array(items)))
 }
 
-pub fn handle_on_type_formatting(
+pub fn handle_code_action(
     world: ServerWorld,
-    params: req::DocumentOnTypeFormattingParams,
-) -> Result<Option<Vec<TextEdit>>> {
-    if params.ch != "=" {
-        return Ok(None);
-    }
-
+    params: req::CodeActionParams,
+) -> Result<Option<Vec<Command>>> {
     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 file = world.analysis().file_syntax(file_id)?;
-    let action = match libeditor::on_eq_typed(&file, offset) {
-        None => return Ok(None),
-        Some(action) => action,
-    };
-    Ok(Some(action.edit.conv_with(&line_index)))
-}
-
-pub fn handle_execute_command(
-    world: ServerWorld,
-    mut params: req::ExecuteCommandParams,
-) -> Result<(req::ApplyWorkspaceEditParams, Option<Position>)> {
-    if params.command.as_str() != "apply_code_action" {
-        bail!("unknown cmd: {:?}", params.command);
-    }
-    if params.arguments.len() != 1 {
-        bail!("expected single arg, got {}", params.arguments.len());
-    }
-    let arg = params.arguments.pop().unwrap();
-    let arg: ActionRequest = from_value(arg)?;
-    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()),
-        ActionId::AddImpl => libeditor::add_impl(&file, arg.offset).map(|f| f()),
-    }.ok_or_else(|| format_err!("command not applicable"))?;
-    let line_index = world.analysis().file_line_index(file_id)?;
-    let mut changes = HashMap::new();
-    changes.insert(
-        arg.text_document.uri,
-        action_result.edit.conv_with(&line_index),
-    );
-    let edit = WorkspaceEdit {
-        changes: Some(changes),
-        document_changes: None,
-    };
-    let edit = req::ApplyWorkspaceEditParams { edit };
-    let cursor_pos = action_result.cursor_position
-        .map(|off| off.conv_with(&line_index));
-    Ok((edit, cursor_pos))
-}
+    let line_index = world.analysis().file_line_index(file_id);
+    let offset = params.range.conv_with(&line_index).start();
 
-#[derive(Serialize, Deserialize)]
-struct ActionRequest {
-    id: ActionId,
-    text_document: TextDocumentIdentifier,
-    offset: TextUnit,
-}
+    let assists = world.analysis().assists(file_id, offset).into_iter();
+    let fixes = world.analysis().diagnostics(file_id).into_iter()
+        .filter_map(|d| Some((d.range, d.fix?)))
+        .filter(|(range, _fix)| contains_offset_nonstrict(*range, offset))
+        .map(|(_range, fix)| fix);
 
-fn apply_code_action_cmd(id: ActionId, doc: TextDocumentIdentifier, offset: TextUnit) -> Command {
-    let action_request = ActionRequest { id, text_document: doc, offset };
-    Command {
-        title: id.title().to_string(),
-        command: "apply_code_action".to_string(),
-        arguments: Some(vec![to_value(action_request).unwrap()]),
+    let mut res = Vec::new();
+    for source_edit in assists.chain(fixes) {
+        let title = source_edit.label.clone();
+        let edit = source_edit.try_conv_with(&world)?;
+        let cmd = Command {
+            title,
+            command: "libsyntax-rust.applySourceChange".to_string(),
+            arguments: Some(vec![to_value(edit).unwrap()]),
+        };
+        res.push(cmd);
     }
-}
 
-#[derive(Serialize, Deserialize, Clone, Copy)]
-enum ActionId {
-    FlipComma,
-    AddDerive,
-    AddImpl,
-}
-
-impl ActionId {
-    fn title(&self) -> &'static str {
-        match *self {
-            ActionId::FlipComma => "Flip `,`",
-            ActionId::AddDerive => "Add `#[derive]`",
-            ActionId::AddImpl => "Add impl",
-        }
-    }
+    Ok(Some(res))
 }
 
 pub fn publish_diagnostics(
@@ -440,28 +328,20 @@ pub fn publish_diagnostics(
     uri: Url
 ) -> Result<req::PublishDiagnosticsParams> {
     let file_id = world.uri_to_file_id(&uri)?;
-    let line_index = world.analysis().file_line_index(file_id)?;
-    let diagnostics = world.analysis().diagnostics(file_id)?
+    let line_index = world.analysis().file_line_index(file_id);
+    let diagnostics = world.analysis().diagnostics(file_id)
         .into_iter()
-        .map(|(d, _quick_fix)| Diagnostic {
+        .map(|d| Diagnostic {
             range: d.range.conv_with(&line_index),
             severity: Some(DiagnosticSeverity::Error),
             code: None,
             source: Some("libsyntax2".to_string()),
-            message: d.msg,
+            message: d.message,
             related_information: None,
         }).collect();
     Ok(req::PublishDiagnosticsParams { uri, diagnostics })
 }
 
-pub fn handle_decorations(
-    world: ServerWorld,
-    params: TextDocumentIdentifier,
-) -> Result<Vec<Decoration>> {
-    let file_id = params.try_conv_with(&world)?;
-    highlight(&world, file_id)
-}
-
 pub fn publish_decorations(
     world: ServerWorld,
     uri: Url
@@ -469,18 +349,16 @@ pub fn publish_decorations(
     let file_id = world.uri_to_file_id(&uri)?;
     Ok(req::PublishDecorationsParams {
         uri,
-        decorations: highlight(&world, file_id)?
+        decorations: highlight(&world, file_id),
     })
 }
 
-fn highlight(world: &ServerWorld, file_id: FileId) -> Result<Vec<Decoration>> {
-    let file = world.analysis().file_syntax(file_id)?;
-    let line_index = world.analysis().file_line_index(file_id)?;
-    let res = libeditor::highlight(&file)
+fn highlight(world: &ServerWorld, file_id: FileId) -> Vec<Decoration> {
+    let line_index = world.analysis().file_line_index(file_id);
+    world.analysis().highlight(file_id)
         .into_iter()
         .map(|h| Decoration {
             range: h.range.conv_with(&line_index),
             tag: h.tag,
-        }).collect();
-    Ok(res)
+        }).collect()
 }
diff --git a/crates/server/src/main_loop/mod.rs b/crates/server/src/main_loop/mod.rs
index accb13878..0f66248a5 100644
--- a/crates/server/src/main_loop/mod.rs
+++ b/crates/server/src/main_loop/mod.rs
@@ -7,7 +7,6 @@ use std::{
 use threadpool::ThreadPool;
 use crossbeam_channel::{Sender, Receiver};
 use languageserver_types::Url;
-use serde_json::to_value;
 
 use {
     req, dispatch,
@@ -15,24 +14,6 @@ use {
     io::{Io, RawMsg, RawRequest, RawNotification},
     vfs::FileEvent,
     server_world::{ServerWorldState, ServerWorld},
-    main_loop::handlers::{
-        handle_syntax_tree,
-        handle_extend_selection,
-        publish_diagnostics,
-        publish_decorations,
-        handle_document_symbol,
-        handle_code_action,
-        handle_execute_command,
-        handle_workspace_symbol,
-        handle_goto_definition,
-        handle_find_matching_brace,
-        handle_parent_module,
-        handle_join_lines,
-        handle_completion,
-        handle_runnables,
-        handle_decorations,
-        handle_on_type_formatting,
-    },
 };
 
 pub(super) fn main_loop(
@@ -45,7 +26,6 @@ pub(super) fn main_loop(
     info!("server initialized, serving requests");
     let mut state = ServerWorldState::new();
 
-    let mut next_request_id = 0;
     let mut pending_requests: HashSet<u64> = HashSet::new();
     let mut fs_events_receiver = Some(&fs_events_receiver);
     loop {
@@ -78,12 +58,6 @@ pub(super) fn main_loop(
             }
             Event::Task(task) => {
                 match task {
-                    Task::Request(mut request) => {
-                        request.id = next_request_id;
-                        pending_requests.insert(next_request_id);
-                        next_request_id += 1;
-                        io.send(RawMsg::Request(request));
-                    }
                     Task::Respond(response) =>
                         io.send(RawMsg::Response(response)),
                     Task::Notify(n) =>
@@ -125,79 +99,26 @@ fn on_request(
     sender: &Sender<Task>,
     req: RawRequest,
 ) -> Result<bool> {
-    let mut req = Some(req);
-    handle_request_on_threadpool::<req::SyntaxTree>(
-        &mut req, pool, world, sender, handle_syntax_tree,
-    )?;
-    handle_request_on_threadpool::<req::ExtendSelection>(
-        &mut req, pool, world, sender, handle_extend_selection,
-    )?;
-    handle_request_on_threadpool::<req::FindMatchingBrace>(
-        &mut req, pool, world, sender, handle_find_matching_brace,
-    )?;
-    handle_request_on_threadpool::<req::DocumentSymbolRequest>(
-        &mut req, pool, world, sender, handle_document_symbol,
-    )?;
-    handle_request_on_threadpool::<req::CodeActionRequest>(
-        &mut req, pool, world, sender, handle_code_action,
-    )?;
-    handle_request_on_threadpool::<req::Runnables>(
-        &mut req, pool, world, sender, handle_runnables,
-    )?;
-    handle_request_on_threadpool::<req::WorkspaceSymbol>(
-        &mut req, pool, world, sender, handle_workspace_symbol,
-    )?;
-    handle_request_on_threadpool::<req::GotoDefinition>(
-        &mut req, pool, world, sender, handle_goto_definition,
-    )?;
-    handle_request_on_threadpool::<req::Completion>(
-        &mut req, pool, world, sender, handle_completion,
-    )?;
-    handle_request_on_threadpool::<req::ParentModule>(
-        &mut req, pool, world, sender, handle_parent_module,
-    )?;
-    handle_request_on_threadpool::<req::JoinLines>(
-        &mut req, pool, world, sender, handle_join_lines,
-    )?;
-    handle_request_on_threadpool::<req::DecorationsRequest>(
-        &mut req, pool, world, sender, handle_decorations,
-    )?;
-    handle_request_on_threadpool::<req::OnTypeFormatting>(
-        &mut req, pool, world, sender, handle_on_type_formatting,
-    )?;
-    dispatch::handle_request::<req::ExecuteCommand, _>(&mut req, |params, resp| {
-        io.send(RawMsg::Response(resp.into_response(Ok(None))?));
-
-        let world = world.snapshot();
-        let sender = sender.clone();
-        pool.execute(move || {
-            let (edit, cursor) = match handle_execute_command(world, params) {
-                Ok(res) => res,
-                Err(e) => return sender.send(Task::Die(e)),
-            };
-            match to_value(edit) {
-                Err(e) => return sender.send(Task::Die(e.into())),
-                Ok(params) => {
-                    let request = RawRequest {
-                        id: 0,
-                        method: <req::ApplyWorkspaceEdit as req::ClientRequest>::METHOD.to_string(),
-                        params,
-                    };
-                    sender.send(Task::Request(request))
-                }
-            }
-            if let Some(cursor) = cursor {
-                let request = RawRequest {
-                    id: 0,
-                    method: <req::MoveCursor as req::ClientRequest>::METHOD.to_string(),
-                    params: to_value(cursor).unwrap(),
-                };
-                sender.send(Task::Request(request))
-            }
-        });
-        Ok(())
-    })?;
+    let mut pool_dispatcher = PoolDispatcher {
+        req: Some(req),
+        pool, world, sender
+    };
+    pool_dispatcher
+        .on::<req::SyntaxTree>(handlers::handle_syntax_tree)?
+        .on::<req::ExtendSelection>(handlers::handle_extend_selection)?
+        .on::<req::FindMatchingBrace>(handlers::handle_find_matching_brace)?
+        .on::<req::JoinLines>(handlers::handle_join_lines)?
+        .on::<req::OnTypeFormatting>(handlers::handle_on_type_formatting)?
+        .on::<req::DocumentSymbolRequest>(handlers::handle_document_symbol)?
+        .on::<req::WorkspaceSymbol>(handlers::handle_workspace_symbol)?
+        .on::<req::GotoDefinition>(handlers::handle_goto_definition)?
+        .on::<req::ParentModule>(handlers::handle_parent_module)?
+        .on::<req::Runnables>(handlers::handle_runnables)?
+        .on::<req::DecorationsRequest>(handlers::handle_decorations)?
+        .on::<req::Completion>(handlers::handle_completion)?
+        .on::<req::CodeActionRequest>(handlers::handle_code_action)?;
 
+    let mut req = pool_dispatcher.req;
     let mut shutdown = false;
     dispatch::handle_request::<req::Shutdown, _>(&mut req, |(), resp| {
         let resp = resp.into_response(Ok(()))?;
@@ -273,27 +194,33 @@ fn on_notification(
     Ok(())
 }
 
-fn handle_request_on_threadpool<R: req::ClientRequest>(
-    req: &mut Option<RawRequest>,
-    pool: &ThreadPool,
-    world: &ServerWorldState,
-    sender: &Sender<Task>,
-    f: fn(ServerWorld, R::Params) -> Result<R::Result>,
-) -> Result<()>
-{
-    dispatch::handle_request::<R, _>(req, |params, resp| {
-        let world = world.snapshot();
-        let sender = sender.clone();
-        pool.execute(move || {
-            let res = f(world, params);
-            let task = match resp.into_response(res) {
-                Ok(resp) => Task::Respond(resp),
-                Err(e) => Task::Die(e),
-            };
-            sender.send(task);
-        });
-        Ok(())
-    })
+struct PoolDispatcher<'a> {
+    req: Option<RawRequest>,
+    pool: &'a ThreadPool,
+    world: &'a ServerWorldState,
+    sender: &'a Sender<Task>,
+}
+
+impl<'a> PoolDispatcher<'a> {
+    fn on<'b, R: req::ClientRequest>(&'b mut self, f: fn(ServerWorld, R::Params) -> Result<R::Result>) -> Result<&'b mut Self> {
+        let world = self.world;
+        let sender = self.sender;
+        let pool = self.pool;
+        dispatch::handle_request::<R, _>(&mut self.req, |params, resp| {
+            let world = world.snapshot();
+            let sender = sender.clone();
+            pool.execute(move || {
+                let res = f(world, params);
+                let task = match resp.into_response(res) {
+                    Ok(resp) => Task::Respond(resp),
+                    Err(e) => Task::Die(e),
+                };
+                sender.send(task);
+            });
+            Ok(())
+        })?;
+        Ok(self)
+    }
 }
 
 fn update_file_notifications_on_threadpool(
@@ -303,7 +230,7 @@ fn update_file_notifications_on_threadpool(
     uri: Url,
 ) {
     pool.execute(move || {
-        match publish_diagnostics(world.clone(), uri.clone()) {
+        match handlers::publish_diagnostics(world.clone(), uri.clone()) {
             Err(e) => {
                 error!("failed to compute diagnostics: {:?}", e)
             }
@@ -312,7 +239,7 @@ fn update_file_notifications_on_threadpool(
                 sender.send(Task::Notify(not));
             }
         }
-        match publish_decorations(world, uri) {
+        match handlers::publish_decorations(world, uri) {
             Err(e) => {
                 error!("failed to compute decorations: {:?}", e)
             }
-- 
cgit v1.2.3